##// END OF EJS Templates
protocol: unify changegroup commands...
Matt Mackall -
r11584:1af96b09 default
parent child Browse files
Show More
@@ -1,320 +1,330
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.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 import os
9 import os, zlib
10 10 from mercurial import ui, hg, hook, error, encoding, templater, wireproto
11 11 from common import get_mtime, ErrorResponse, permhooks
12 12 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
13 13 from request import wsgirequest
14 14 import webcommands, protocol, webutil
15 15
16 16 perms = {
17 17 'changegroup': 'pull',
18 18 'changegroupsubset': 'pull',
19 19 'stream_out': 'pull',
20 20 'listkeys': 'pull',
21 21 'unbundle': 'push',
22 22 'pushkey': 'push',
23 23 }
24 24
25 HGTYPE = 'application/mercurial-0.1'
25 26 class webproto(object):
26 27 def __init__(self, req):
27 28 self.req = req
28 29 self.response = ''
29 30 def getargs(self, args):
30 31 data = {}
31 32 keys = args.split()
32 33 for k in keys:
33 34 if k == '*':
34 35 star = {}
35 36 for key in self.req.form.keys():
36 37 if key not in keys:
37 38 star[key] = self.req.form[key][0]
38 39 data['*'] = star
39 40 else:
40 41 data[k] = self.req.form[k][0]
41 42 return [data[k] for k in keys]
43 def sendchangegroup(self, cg):
44 self.req.respond(HTTP_OK, HGTYPE)
45 z = zlib.compressobj()
46 while 1:
47 chunk = cg.read(4096)
48 if not chunk:
49 break
50 self.req.write(z.compress(chunk))
51 self.req.write(z.flush())
52
42 53 def respond(self, s):
43 HGTYPE = 'application/mercurial-0.1'
44 54 self.req.respond(HTTP_OK, HGTYPE, length=len(s))
45 55 self.response = s
46 56
47 57 def callproto(repo, req, cmd):
48 58 p = webproto(req)
49 59 r = wireproto.dispatch(repo, p, cmd)
50 60 yield p.response
51 61
52 62 class hgweb(object):
53 63 def __init__(self, repo, name=None, baseui=None):
54 64 if isinstance(repo, str):
55 65 if baseui:
56 66 u = baseui.copy()
57 67 else:
58 68 u = ui.ui()
59 69 self.repo = hg.repository(u, repo)
60 70 else:
61 71 self.repo = repo
62 72
63 73 self.repo.ui.setconfig('ui', 'report_untrusted', 'off')
64 74 self.repo.ui.setconfig('ui', 'interactive', 'off')
65 75 hook.redirect(True)
66 76 self.mtime = -1
67 77 self.reponame = name
68 78 self.archives = 'zip', 'gz', 'bz2'
69 79 self.stripecount = 1
70 80 # a repo owner may set web.templates in .hg/hgrc to get any file
71 81 # readable by the user running the CGI script
72 82 self.templatepath = self.config('web', 'templates')
73 83
74 84 # The CGI scripts are often run by a user different from the repo owner.
75 85 # Trust the settings from the .hg/hgrc files by default.
76 86 def config(self, section, name, default=None, untrusted=True):
77 87 return self.repo.ui.config(section, name, default,
78 88 untrusted=untrusted)
79 89
80 90 def configbool(self, section, name, default=False, untrusted=True):
81 91 return self.repo.ui.configbool(section, name, default,
82 92 untrusted=untrusted)
83 93
84 94 def configlist(self, section, name, default=None, untrusted=True):
85 95 return self.repo.ui.configlist(section, name, default,
86 96 untrusted=untrusted)
87 97
88 98 def refresh(self, request=None):
89 99 if request:
90 100 self.repo.ui.environ = request.env
91 101 mtime = get_mtime(self.repo.spath)
92 102 if mtime != self.mtime:
93 103 self.mtime = mtime
94 104 self.repo = hg.repository(self.repo.ui, self.repo.root)
95 105 self.maxchanges = int(self.config("web", "maxchanges", 10))
96 106 self.stripecount = int(self.config("web", "stripes", 1))
97 107 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
98 108 self.maxfiles = int(self.config("web", "maxfiles", 10))
99 109 self.allowpull = self.configbool("web", "allowpull", True)
100 110 encoding.encoding = self.config("web", "encoding",
101 111 encoding.encoding)
102 112
103 113 def run(self):
104 114 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
105 115 raise RuntimeError("This function is only intended to be "
106 116 "called while running as a CGI script.")
107 117 import mercurial.hgweb.wsgicgi as wsgicgi
108 118 wsgicgi.launch(self)
109 119
110 120 def __call__(self, env, respond):
111 121 req = wsgirequest(env, respond)
112 122 return self.run_wsgi(req)
113 123
114 124 def run_wsgi(self, req):
115 125
116 126 self.refresh(req)
117 127
118 128 # work with CGI variables to create coherent structure
119 129 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
120 130
121 131 req.url = req.env['SCRIPT_NAME']
122 132 if not req.url.endswith('/'):
123 133 req.url += '/'
124 134 if 'REPO_NAME' in req.env:
125 135 req.url += req.env['REPO_NAME'] + '/'
126 136
127 137 if 'PATH_INFO' in req.env:
128 138 parts = req.env['PATH_INFO'].strip('/').split('/')
129 139 repo_parts = req.env.get('REPO_NAME', '').split('/')
130 140 if parts[:len(repo_parts)] == repo_parts:
131 141 parts = parts[len(repo_parts):]
132 142 query = '/'.join(parts)
133 143 else:
134 144 query = req.env['QUERY_STRING'].split('&', 1)[0]
135 145 query = query.split(';', 1)[0]
136 146
137 147 # process this if it's a protocol request
138 148 # protocol bits don't need to create any URLs
139 149 # and the clients always use the old URL structure
140 150
141 151 cmd = req.form.get('cmd', [''])[0]
142 152 if cmd and cmd in protocol.__all__:
143 153 if query:
144 154 raise ErrorResponse(HTTP_NOT_FOUND)
145 155 try:
146 156 if cmd in perms:
147 157 try:
148 158 self.check_perm(req, perms[cmd])
149 159 except ErrorResponse, inst:
150 160 if cmd == 'unbundle':
151 161 req.drain()
152 162 raise
153 163 if cmd in wireproto.commands:
154 164 return callproto(self.repo, req, cmd)
155 165 method = getattr(protocol, cmd)
156 166 return method(self.repo, req)
157 167 except ErrorResponse, inst:
158 168 req.respond(inst, protocol.HGTYPE)
159 169 if not inst.message:
160 170 return []
161 171 return '0\n%s\n' % inst.message,
162 172
163 173 # translate user-visible url structure to internal structure
164 174
165 175 args = query.split('/', 2)
166 176 if 'cmd' not in req.form and args and args[0]:
167 177
168 178 cmd = args.pop(0)
169 179 style = cmd.rfind('-')
170 180 if style != -1:
171 181 req.form['style'] = [cmd[:style]]
172 182 cmd = cmd[style + 1:]
173 183
174 184 # avoid accepting e.g. style parameter as command
175 185 if hasattr(webcommands, cmd):
176 186 req.form['cmd'] = [cmd]
177 187 else:
178 188 cmd = ''
179 189
180 190 if cmd == 'static':
181 191 req.form['file'] = ['/'.join(args)]
182 192 else:
183 193 if args and args[0]:
184 194 node = args.pop(0)
185 195 req.form['node'] = [node]
186 196 if args:
187 197 req.form['file'] = args
188 198
189 199 ua = req.env.get('HTTP_USER_AGENT', '')
190 200 if cmd == 'rev' and 'mercurial' in ua:
191 201 req.form['style'] = ['raw']
192 202
193 203 if cmd == 'archive':
194 204 fn = req.form['node'][0]
195 205 for type_, spec in self.archive_specs.iteritems():
196 206 ext = spec[2]
197 207 if fn.endswith(ext):
198 208 req.form['node'] = [fn[:-len(ext)]]
199 209 req.form['type'] = [type_]
200 210
201 211 # process the web interface request
202 212
203 213 try:
204 214 tmpl = self.templater(req)
205 215 ctype = tmpl('mimetype', encoding=encoding.encoding)
206 216 ctype = templater.stringify(ctype)
207 217
208 218 # check read permissions non-static content
209 219 if cmd != 'static':
210 220 self.check_perm(req, None)
211 221
212 222 if cmd == '':
213 223 req.form['cmd'] = [tmpl.cache['default']]
214 224 cmd = req.form['cmd'][0]
215 225
216 226 if cmd not in webcommands.__all__:
217 227 msg = 'no such method: %s' % cmd
218 228 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
219 229 elif cmd == 'file' and 'raw' in req.form.get('style', []):
220 230 self.ctype = ctype
221 231 content = webcommands.rawfile(self, req, tmpl)
222 232 else:
223 233 content = getattr(webcommands, cmd)(self, req, tmpl)
224 234 req.respond(HTTP_OK, ctype)
225 235
226 236 return content
227 237
228 238 except error.LookupError, err:
229 239 req.respond(HTTP_NOT_FOUND, ctype)
230 240 msg = str(err)
231 241 if 'manifest' not in msg:
232 242 msg = 'revision not found: %s' % err.name
233 243 return tmpl('error', error=msg)
234 244 except (error.RepoError, error.RevlogError), inst:
235 245 req.respond(HTTP_SERVER_ERROR, ctype)
236 246 return tmpl('error', error=str(inst))
237 247 except ErrorResponse, inst:
238 248 req.respond(inst, ctype)
239 249 return tmpl('error', error=inst.message)
240 250
241 251 def templater(self, req):
242 252
243 253 # determine scheme, port and server name
244 254 # this is needed to create absolute urls
245 255
246 256 proto = req.env.get('wsgi.url_scheme')
247 257 if proto == 'https':
248 258 proto = 'https'
249 259 default_port = "443"
250 260 else:
251 261 proto = 'http'
252 262 default_port = "80"
253 263
254 264 port = req.env["SERVER_PORT"]
255 265 port = port != default_port and (":" + port) or ""
256 266 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
257 267 staticurl = self.config("web", "staticurl") or req.url + 'static/'
258 268 if not staticurl.endswith('/'):
259 269 staticurl += '/'
260 270
261 271 # some functions for the templater
262 272
263 273 def header(**map):
264 274 yield tmpl('header', encoding=encoding.encoding, **map)
265 275
266 276 def footer(**map):
267 277 yield tmpl("footer", **map)
268 278
269 279 def motd(**map):
270 280 yield self.config("web", "motd", "")
271 281
272 282 # figure out which style to use
273 283
274 284 vars = {}
275 285 styles = (
276 286 req.form.get('style', [None])[0],
277 287 self.config('web', 'style'),
278 288 'paper',
279 289 )
280 290 style, mapfile = templater.stylemap(styles, self.templatepath)
281 291 if style == styles[0]:
282 292 vars['style'] = style
283 293
284 294 start = req.url[-1] == '?' and '&' or '?'
285 295 sessionvars = webutil.sessionvars(vars, start)
286 296
287 297 if not self.reponame:
288 298 self.reponame = (self.config("web", "name")
289 299 or req.env.get('REPO_NAME')
290 300 or req.url.strip('/') or self.repo.root)
291 301
292 302 # create the templater
293 303
294 304 tmpl = templater.templater(mapfile,
295 305 defaults={"url": req.url,
296 306 "staticurl": staticurl,
297 307 "urlbase": urlbase,
298 308 "repo": self.reponame,
299 309 "header": header,
300 310 "footer": footer,
301 311 "motd": motd,
302 312 "sessionvars": sessionvars
303 313 })
304 314 return tmpl
305 315
306 316 def archivelist(self, nodeid):
307 317 allowed = self.configlist("web", "allow_archive")
308 318 for i, spec in self.archive_specs.iteritems():
309 319 if i in allowed or self.configbool("web", "allow" + i):
310 320 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
311 321
312 322 archive_specs = {
313 323 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
314 324 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
315 325 'zip': ('application/zip', 'zip', '.zip', None),
316 326 }
317 327
318 328 def check_perm(self, req, op):
319 329 for hook in permhooks:
320 330 hook(self, req, op)
@@ -1,161 +1,124
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import cStringIO, zlib, tempfile, errno, os, sys, urllib, copy
9 9 from mercurial import util, streamclone, pushkey
10 10 from mercurial.node import bin, hex
11 11 from mercurial import changegroup as changegroupmod
12 12 from common import ErrorResponse, HTTP_OK, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
13 13
14 14 # __all__ is populated with the allowed commands. Be sure to add to it if
15 15 # you're adding a new command, or the new command won't work.
16 16
17 17 __all__ = [
18 18 'lookup', 'heads', 'branches', 'between', 'changegroup',
19 19 'changegroupsubset', 'capabilities', 'unbundle', 'stream_out',
20 20 'branchmap', 'pushkey', 'listkeys'
21 21 ]
22 22
23 23 HGTYPE = 'application/mercurial-0.1'
24 24 basecaps = 'lookup changegroupsubset branchmap pushkey'.split()
25 25
26 def changegroup(repo, req):
27 req.respond(HTTP_OK, HGTYPE)
28 nodes = []
29
30 if 'roots' in req.form:
31 nodes = map(bin, req.form['roots'][0].split(" "))
32
33 z = zlib.compressobj()
34 f = repo.changegroup(nodes, 'serve')
35 while 1:
36 chunk = f.read(4096)
37 if not chunk:
38 break
39 yield z.compress(chunk)
40
41 yield z.flush()
42
43 def changegroupsubset(repo, req):
44 req.respond(HTTP_OK, HGTYPE)
45 bases = []
46 heads = []
47
48 if 'bases' in req.form:
49 bases = [bin(x) for x in req.form['bases'][0].split(' ')]
50 if 'heads' in req.form:
51 heads = [bin(x) for x in req.form['heads'][0].split(' ')]
52
53 z = zlib.compressobj()
54 f = repo.changegroupsubset(bases, heads, 'serve')
55 while 1:
56 chunk = f.read(4096)
57 if not chunk:
58 break
59 yield z.compress(chunk)
60
61 yield z.flush()
62
63 26 def capabilities(repo, req):
64 27 caps = copy.copy(basecaps)
65 28 if streamclone.allowed(repo.ui):
66 29 caps.append('stream=%d' % repo.changelog.version)
67 30 if changegroupmod.bundlepriority:
68 31 caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
69 32 rsp = ' '.join(caps)
70 33 req.respond(HTTP_OK, HGTYPE, length=len(rsp))
71 34 yield rsp
72 35
73 36 def unbundle(repo, req):
74 37
75 38 proto = req.env.get('wsgi.url_scheme') or 'http'
76 39 their_heads = req.form['heads'][0].split(' ')
77 40
78 41 def check_heads():
79 42 heads = map(hex, repo.heads())
80 43 return their_heads == [hex('force')] or their_heads == heads
81 44
82 45 # fail early if possible
83 46 if not check_heads():
84 47 req.drain()
85 48 raise ErrorResponse(HTTP_OK, 'unsynced changes')
86 49
87 50 # do not lock repo until all changegroup data is
88 51 # streamed. save to temporary file.
89 52
90 53 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
91 54 fp = os.fdopen(fd, 'wb+')
92 55 try:
93 56 length = int(req.env['CONTENT_LENGTH'])
94 57 for s in util.filechunkiter(req, limit=length):
95 58 fp.write(s)
96 59
97 60 try:
98 61 lock = repo.lock()
99 62 try:
100 63 if not check_heads():
101 64 raise ErrorResponse(HTTP_OK, 'unsynced changes')
102 65
103 66 fp.seek(0)
104 67 header = fp.read(6)
105 68 if header.startswith('HG') and not header.startswith('HG10'):
106 69 raise ValueError('unknown bundle version')
107 70 elif header not in changegroupmod.bundletypes:
108 71 raise ValueError('unknown bundle compression type')
109 72 gen = changegroupmod.unbundle(header, fp)
110 73
111 74 # send addchangegroup output to client
112 75
113 76 oldio = sys.stdout, sys.stderr
114 77 sys.stderr = sys.stdout = cStringIO.StringIO()
115 78
116 79 try:
117 80 url = 'remote:%s:%s:%s' % (
118 81 proto,
119 82 urllib.quote(req.env.get('REMOTE_HOST', '')),
120 83 urllib.quote(req.env.get('REMOTE_USER', '')))
121 84 try:
122 85 ret = repo.addchangegroup(gen, 'serve', url, lock=lock)
123 86 except util.Abort, inst:
124 87 sys.stdout.write("abort: %s\n" % inst)
125 88 ret = 0
126 89 finally:
127 90 val = sys.stdout.getvalue()
128 91 sys.stdout, sys.stderr = oldio
129 92 req.respond(HTTP_OK, HGTYPE)
130 93 return '%d\n%s' % (ret, val),
131 94 finally:
132 95 lock.release()
133 96 except ValueError, inst:
134 97 raise ErrorResponse(HTTP_OK, inst)
135 98 except (OSError, IOError), inst:
136 99 error = getattr(inst, 'strerror', 'Unknown error')
137 100 if not isinstance(error, str):
138 101 error = 'Error: %s' % str(error)
139 102 if inst.errno == errno.ENOENT:
140 103 code = HTTP_NOT_FOUND
141 104 else:
142 105 code = HTTP_SERVER_ERROR
143 106 filename = getattr(inst, 'filename', '')
144 107 # Don't send our filesystem layout to the client
145 108 if filename and filename.startswith(repo.root):
146 109 filename = filename[len(repo.root)+1:]
147 110 text = '%s: %s' % (error, filename)
148 111 else:
149 112 text = error.replace(repo.root + os.path.sep, '')
150 113 raise ErrorResponse(code, text)
151 114 finally:
152 115 fp.close()
153 116 os.unlink(tempname)
154 117
155 118 def stream_out(repo, req):
156 119 req.respond(HTTP_OK, HGTYPE)
157 120 try:
158 121 for chunk in streamclone.stream_out(repo):
159 122 yield chunk
160 123 except streamclone.StreamException, inst:
161 124 yield str(inst)
@@ -1,207 +1,188
1 1 # sshserver.py - ssh protocol server support for mercurial
2 2 #
3 3 # Copyright 2005-2007 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 i18n import _
10 10 from node import bin, hex
11 11 import streamclone, util, hook, pushkey, wireproto
12 12 import os, sys, tempfile, urllib, copy
13 13
14 14 class sshserver(object):
15 15
16 16 caps = 'unbundle lookup changegroupsubset branchmap pushkey'.split()
17 17
18 18 def __init__(self, ui, repo):
19 19 self.ui = ui
20 20 self.repo = repo
21 21 self.lock = None
22 22 self.fin = sys.stdin
23 23 self.fout = sys.stdout
24 24
25 25 hook.redirect(True)
26 26 sys.stdout = sys.stderr
27 27
28 28 # Prevent insertion/deletion of CRs
29 29 util.set_binary(self.fin)
30 30 util.set_binary(self.fout)
31 31
32 32 def getargs(self, args):
33 33 data = {}
34 34 keys = args.split()
35 35 count = len(keys)
36 36 for n in xrange(len(keys)):
37 37 argline = self.fin.readline()[:-1]
38 38 arg, l = argline.split()
39 39 val = self.fin.read(int(l))
40 40 if arg not in keys:
41 41 raise util.Abort("unexpected parameter %r" % arg)
42 42 if arg == '*':
43 43 star = {}
44 44 for n in xrange(int(l)):
45 45 arg, l = argline.split()
46 46 val = self.fin.read(int(l))
47 47 star[arg] = val
48 48 data['*'] = star
49 49 else:
50 50 data[arg] = val
51 51 return [data[k] for k in keys]
52 52
53 53 def getarg(self, name):
54 54 return self.getargs(name)[0]
55 55
56 56 def respond(self, v):
57 57 self.fout.write("%d\n" % len(v))
58 58 self.fout.write(v)
59 59 self.fout.flush()
60 60
61 def sendchangegroup(self, changegroup):
62 while True:
63 d = changegroup.read(4096)
64 if not d:
65 break
66 self.fout.write(d)
67
68 self.fout.flush()
69
61 70 def serve_forever(self):
62 71 try:
63 72 while self.serve_one():
64 73 pass
65 74 finally:
66 75 if self.lock is not None:
67 76 self.lock.release()
68 77 sys.exit(0)
69 78
70 79 def serve_one(self):
71 80 cmd = self.fin.readline()[:-1]
72 81 if cmd and not wireproto.dispatch(self.repo, self, cmd):
73 82 impl = getattr(self, 'do_' + cmd, None)
74 83 if impl:
75 84 r = impl()
76 85 if r is not None:
77 86 self.respond(r)
78 87 else: self.respond("")
79 88 return cmd != ''
80 89
81 90 def do_hello(self):
82 91 '''the hello command returns a set of lines describing various
83 92 interesting things about the server, in an RFC822-like format.
84 93 Currently the only one defined is "capabilities", which
85 94 consists of a line in the form:
86 95
87 96 capabilities: space separated list of tokens
88 97 '''
89 98 caps = copy.copy(self.caps)
90 99 if streamclone.allowed(self.repo.ui):
91 100 caps.append('stream=%d' % self.repo.changelog.version)
92 101 return "capabilities: %s\n" % (' '.join(caps),)
93 102
94 103 def do_lock(self):
95 104 '''DEPRECATED - allowing remote client to lock repo is not safe'''
96 105
97 106 self.lock = self.repo.lock()
98 107 return ""
99 108
100 109 def do_unlock(self):
101 110 '''DEPRECATED'''
102 111
103 112 if self.lock:
104 113 self.lock.release()
105 114 self.lock = None
106 115 return ""
107 116
108 def do_changegroup(self):
109 nodes = []
110 roots = self.getarg('roots')
111 nodes = map(bin, roots.split(" "))
112
113 cg = self.repo.changegroup(nodes, 'serve')
114 while True:
115 d = cg.read(4096)
116 if not d:
117 break
118 self.fout.write(d)
119
120 self.fout.flush()
121
122 def do_changegroupsubset(self):
123 bases, heads = self.getargs('bases heads')
124 bases = [bin(n) for n in bases.split(' ')]
125 heads = [bin(n) for n in heads.split(' ')]
126
127 cg = self.repo.changegroupsubset(bases, heads, 'serve')
128 while True:
129 d = cg.read(4096)
130 if not d:
131 break
132 self.fout.write(d)
133
134 self.fout.flush()
135
136 117 def do_addchangegroup(self):
137 118 '''DEPRECATED'''
138 119
139 120 if not self.lock:
140 121 self.respond("not locked")
141 122 return
142 123
143 124 self.respond("")
144 125 r = self.repo.addchangegroup(self.fin, 'serve', self.client_url(),
145 126 lock=self.lock)
146 127 return str(r)
147 128
148 129 def client_url(self):
149 130 client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
150 131 return 'remote:ssh:' + client
151 132
152 133 def do_unbundle(self):
153 134 their_heads = self.getarg('heads').split()
154 135
155 136 def check_heads():
156 137 heads = map(hex, self.repo.heads())
157 138 return their_heads == [hex('force')] or their_heads == heads
158 139
159 140 # fail early if possible
160 141 if not check_heads():
161 142 self.respond(_('unsynced changes'))
162 143 return
163 144
164 145 self.respond('')
165 146
166 147 # write bundle data to temporary file because it can be big
167 148 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
168 149 fp = os.fdopen(fd, 'wb+')
169 150 try:
170 151 count = int(self.fin.readline())
171 152 while count:
172 153 fp.write(self.fin.read(count))
173 154 count = int(self.fin.readline())
174 155
175 156 was_locked = self.lock is not None
176 157 if not was_locked:
177 158 self.lock = self.repo.lock()
178 159 try:
179 160 if not check_heads():
180 161 # someone else committed/pushed/unbundled while we
181 162 # were transferring data
182 163 self.respond(_('unsynced changes'))
183 164 return
184 165 self.respond('')
185 166
186 167 # push can proceed
187 168
188 169 fp.seek(0)
189 170 r = self.repo.addchangegroup(fp, 'serve', self.client_url(),
190 171 lock=self.lock)
191 172 self.respond(str(r))
192 173 finally:
193 174 if not was_locked:
194 175 self.lock.release()
195 176 self.lock = None
196 177 finally:
197 178 fp.close()
198 179 os.unlink(tempname)
199 180
200 181 def do_stream_out(self):
201 182 try:
202 183 for chunk in streamclone.stream_out(self.repo):
203 184 self.fout.write(chunk)
204 185 self.fout.flush()
205 186 except streamclone.StreamException, inst:
206 187 self.fout.write(str(inst))
207 188 self.fout.flush()
@@ -1,75 +1,90
1 1 # wireproto.py - generic wire protocol support functions
2 2 #
3 3 # Copyright 2005-2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 from node import bin, hex
10 10 import urllib
11 11 import pushkey as pushkey_
12 12
13 13 def dispatch(repo, proto, command):
14 14 if command not in commands:
15 15 return False
16 16 func, spec = commands[command]
17 17 args = proto.getargs(spec)
18 proto.respond(func(repo, proto, *args))
18 r = func(repo, proto, *args)
19 if r != None:
20 proto.respond(r)
19 21 return True
20 22
21 23 def between(repo, proto, pairs):
22 24 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
23 25 r = []
24 26 for b in repo.between(pairs):
25 27 r.append(" ".join(map(hex, b)) + "\n")
26 28 return "".join(r)
27 29
28 30 def branchmap(repo, proto):
29 31 branchmap = repo.branchmap()
30 32 heads = []
31 33 for branch, nodes in branchmap.iteritems():
32 34 branchname = urllib.quote(branch)
33 35 branchnodes = [hex(node) for node in nodes]
34 36 heads.append('%s %s' % (branchname, ' '.join(branchnodes)))
35 37 return '\n'.join(heads)
36 38
37 39 def branches(repo, proto, nodes):
38 40 nodes = map(bin, nodes.split(" "))
39 41 r = []
40 42 for b in repo.branches(nodes):
41 43 r.append(" ".join(map(hex, b)) + "\n")
42 44 return "".join(r)
43 45
46 def changegroup(repo, proto, roots):
47 nodes = map(bin, roots.split(" "))
48 cg = repo.changegroup(nodes, 'serve')
49 proto.sendchangegroup(cg)
50
51 def changegroupsubset(repo, proto, bases, heads):
52 bases = [bin(n) for n in bases.split(' ')]
53 heads = [bin(n) for n in heads.split(' ')]
54 cg = repo.changegroupsubset(bases, heads, 'serve')
55 proto.sendchangegroup(cg)
56
44 57 def heads(repo, proto):
45 58 h = repo.heads()
46 59 return " ".join(map(hex, h)) + "\n"
47 60
48 61 def listkeys(repo, proto, namespace):
49 62 d = pushkey_.list(repo, namespace).items()
50 63 t = '\n'.join(['%s\t%s' % (k.encode('string-escape'),
51 64 v.encode('string-escape')) for k, v in d])
52 65 return t
53 66
54 67 def lookup(repo, proto, key):
55 68 try:
56 69 r = hex(repo.lookup(key))
57 70 success = 1
58 71 except Exception, inst:
59 72 r = str(inst)
60 73 success = 0
61 74 return "%s %s\n" % (success, r)
62 75
63 76 def pushkey(repo, proto, namespace, key, old, new):
64 77 r = pushkey_.push(repo, namespace, key, old, new)
65 78 return '%s\n' % int(r)
66 79
67 80 commands = {
68 81 'between': (between, 'pairs'),
69 82 'branchmap': (branchmap, ''),
70 83 'branches': (branches, 'nodes'),
84 'changegroup': (changegroup, 'roots'),
85 'changegroupsubset': (changegroupsubset, 'bases heads'),
71 86 'heads': (heads, ''),
72 87 'listkeys': (listkeys, 'namespace'),
73 88 'lookup': (lookup, 'key'),
74 89 'pushkey': (pushkey, 'namespace key old new'),
75 90 }
@@ -1,62 +1,62
1 1 #!/bin/sh
2 2 # This is a test of the wire protocol over CGI-based hgweb.
3 3
4 4 echo % initialize repository
5 5 hg init test
6 6 cd test
7 7 echo a > a
8 8 hg ci -Ama
9 9 cd ..
10 10
11 11 cat >hgweb.cgi <<HGWEB
12 12 #!/usr/bin/env python
13 13 #
14 14 # An example CGI script to use hgweb, edit as necessary
15 15
16 16 import cgitb
17 17 cgitb.enable()
18 18
19 19 from mercurial import demandimport; demandimport.enable()
20 20 from mercurial.hgweb import hgweb
21 21 from mercurial.hgweb import wsgicgi
22 22
23 23 application = hgweb("test", "Empty test repository")
24 24 wsgicgi.launch(application)
25 25 HGWEB
26 26 chmod 755 hgweb.cgi
27 27
28 28 DOCUMENT_ROOT="/var/www/hg"; export DOCUMENT_ROOT
29 29 GATEWAY_INTERFACE="CGI/1.1"; export GATEWAY_INTERFACE
30 30 HTTP_ACCEPT="text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"; export HTTP_ACCEPT
31 31 HTTP_ACCEPT_CHARSET="ISO-8859-1,utf-8;q=0.7,*;q=0.7"; export HTTP_ACCEPT_CHARSET
32 32 HTTP_ACCEPT_ENCODING="gzip,deflate"; export HTTP_ACCEPT_ENCODING
33 33 HTTP_ACCEPT_LANGUAGE="en-us,en;q=0.5"; export HTTP_ACCEPT_LANGUAGE
34 34 HTTP_CACHE_CONTROL="max-age=0"; export HTTP_CACHE_CONTROL
35 35 HTTP_CONNECTION="keep-alive"; export HTTP_CONNECTION
36 36 HTTP_HOST="hg.omnifarious.org"; export HTTP_HOST
37 37 HTTP_KEEP_ALIVE="300"; export HTTP_KEEP_ALIVE
38 38 HTTP_USER_AGENT="Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.8.0.4) Gecko/20060608 Ubuntu/dapper-security Firefox/1.5.0.4"; export HTTP_USER_AGENT
39 39 PATH_INFO="/"; export PATH_INFO
40 40 PATH_TRANSLATED="/var/www/hg/index.html"; export PATH_TRANSLATED
41 41 REMOTE_ADDR="127.0.0.2"; export REMOTE_ADDR
42 42 REMOTE_PORT="44703"; export REMOTE_PORT
43 43 REQUEST_METHOD="GET"; export REQUEST_METHOD
44 44 REQUEST_URI="/test/"; export REQUEST_URI
45 45 SCRIPT_FILENAME="/home/hopper/hg_public/test.cgi"; export SCRIPT_FILENAME
46 46 SCRIPT_NAME="/test"; export SCRIPT_NAME
47 47 SCRIPT_URI="http://hg.omnifarious.org/test/"; export SCRIPT_URI
48 48 SCRIPT_URL="/test/"; export SCRIPT_URL
49 49 SERVER_ADDR="127.0.0.1"; export SERVER_ADDR
50 50 SERVER_ADMIN="eric@localhost"; export SERVER_ADMIN
51 51 SERVER_NAME="hg.omnifarious.org"; export SERVER_NAME
52 52 SERVER_PORT="80"; export SERVER_PORT
53 53 SERVER_PROTOCOL="HTTP/1.1"; export SERVER_PROTOCOL
54 54 SERVER_SIGNATURE="<address>Apache/2.0.53 (Fedora) Server at hg.omnifarious.org Port 80</address>"; export SERVER_SIGNATURE
55 55 SERVER_SOFTWARE="Apache/2.0.53 (Fedora)"; export SERVER_SOFTWARE
56 56
57 57 echo % try hgweb request
58 QUERY_STRING="cmd=changegroup"; export QUERY_STRING
58 QUERY_STRING="cmd=changegroup&roots=0000000000000000000000000000000000000000"; export QUERY_STRING
59 59 python hgweb.cgi >page1 2>&1 ; echo $?
60 60 python "$TESTDIR/md5sum.py" page1
61 61
62 62 exit 0
@@ -1,64 +1,64
1 1 #!/bin/sh
2 2 # An attempt at more fully testing the hgweb web interface.
3 3 # The following things are tested elsewhere and are therefore omitted:
4 4 # - archive, tested in test-archive
5 5 # - unbundle, tested in test-push-http
6 6 # - changegroupsubset, tested in test-pull
7 7
8 8 echo % Set up the repo
9 9 hg init test
10 10 cd test
11 11 mkdir da
12 12 echo foo > da/foo
13 13 echo foo > foo
14 14 hg ci -Ambase
15 15 hg tag 1.0
16 16 echo another > foo
17 17 hg branch stable
18 18 hg ci -Ambranch
19 19 hg serve --config server.uncompressed=False -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
20 20 cat hg.pid >> $DAEMON_PIDS
21 21
22 22 echo % Logs and changes
23 23 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
24 24 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
25 25 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log/1/foo/?style=atom' | sed "s/http:\/\/[^/]*\//http:\/\/127.0.0.1\//"
26 26 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/shortlog/'
27 27 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/0/'
28 28 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/rev/1/?style=raw'
29 29 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/log?rev=base'
30 30
31 31 echo % File-related
32 32 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo/?style=raw'
33 33 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/annotate/1/foo/?style=raw'
34 34 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/?style=raw'
35 35 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/file/1/foo'
36 36 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/filediff/1/foo/?style=raw'
37 37
38 38 echo % Overviews
39 39 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/raw-tags'
40 40 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/raw-branches'
41 41 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/summary/?style=gitweb'
42 42 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/graph/?style=gitweb'
43 43
44 44 echo % capabilities
45 45 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=capabilities'
46 46 echo % heads
47 47 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=heads'
48 48 echo % lookup
49 49 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=lookup&key=1'
50 50 echo % branches
51 51 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=branches&nodes=0000000000000000000000000000000000000000'
52 52 echo % changegroup
53 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=changegroup' \
53 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=changegroup&roots=0000000000000000000000000000000000000000' \
54 54 | $TESTDIR/printrepr.py
55 55 echo % stream_out
56 56 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=stream_out'
57 57 echo % failing unbundle, requires POST request
58 58 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '?cmd=unbundle'
59 59
60 60 echo % Static files
61 61 "$TESTDIR/get-with-headers.py" 127.0.0.1:$HGPORT '/static/style.css'
62 62
63 63 echo % ERRORS ENCOUNTERED
64 64 cat errors.log
General Comments 0
You need to be logged in to leave comments. Login now