##// END OF EJS Templates
hook: redirect stdout to stderr for ssh and http servers
Matt Mackall -
r5833:323b9c55 default
parent child Browse files
Show More
@@ -1,909 +1,910
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 import os, mimetypes, re, mimetools, cStringIO
9 import os, mimetypes, re, mimetools, cStringIO
10 from mercurial.node import *
10 from mercurial.node import *
11 from mercurial import mdiff, ui, hg, util, archival, patch
11 from mercurial import mdiff, ui, hg, util, archival, patch, hook
12 from mercurial import revlog, templater
12 from mercurial import revlog, templater
13 from common import ErrorResponse, get_mtime, style_map, paritygen, get_contact
13 from common import ErrorResponse, get_mtime, style_map, paritygen, get_contact
14 from request import wsgirequest
14 from request import wsgirequest
15 import webcommands, protocol
15 import webcommands, protocol
16
16
17 shortcuts = {
17 shortcuts = {
18 'cl': [('cmd', ['changelog']), ('rev', None)],
18 'cl': [('cmd', ['changelog']), ('rev', None)],
19 'sl': [('cmd', ['shortlog']), ('rev', None)],
19 'sl': [('cmd', ['shortlog']), ('rev', None)],
20 'cs': [('cmd', ['changeset']), ('node', None)],
20 'cs': [('cmd', ['changeset']), ('node', None)],
21 'f': [('cmd', ['file']), ('filenode', None)],
21 'f': [('cmd', ['file']), ('filenode', None)],
22 'fl': [('cmd', ['filelog']), ('filenode', None)],
22 'fl': [('cmd', ['filelog']), ('filenode', None)],
23 'fd': [('cmd', ['filediff']), ('node', None)],
23 'fd': [('cmd', ['filediff']), ('node', None)],
24 'fa': [('cmd', ['annotate']), ('filenode', None)],
24 'fa': [('cmd', ['annotate']), ('filenode', None)],
25 'mf': [('cmd', ['manifest']), ('manifest', None)],
25 'mf': [('cmd', ['manifest']), ('manifest', None)],
26 'ca': [('cmd', ['archive']), ('node', None)],
26 'ca': [('cmd', ['archive']), ('node', None)],
27 'tags': [('cmd', ['tags'])],
27 'tags': [('cmd', ['tags'])],
28 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
28 'tip': [('cmd', ['changeset']), ('node', ['tip'])],
29 'static': [('cmd', ['static']), ('file', None)]
29 'static': [('cmd', ['static']), ('file', None)]
30 }
30 }
31
31
32 def _up(p):
32 def _up(p):
33 if p[0] != "/":
33 if p[0] != "/":
34 p = "/" + p
34 p = "/" + p
35 if p[-1] == "/":
35 if p[-1] == "/":
36 p = p[:-1]
36 p = p[:-1]
37 up = os.path.dirname(p)
37 up = os.path.dirname(p)
38 if up == "/":
38 if up == "/":
39 return "/"
39 return "/"
40 return up + "/"
40 return up + "/"
41
41
42 def revnavgen(pos, pagelen, limit, nodefunc):
42 def revnavgen(pos, pagelen, limit, nodefunc):
43 def seq(factor, limit=None):
43 def seq(factor, limit=None):
44 if limit:
44 if limit:
45 yield limit
45 yield limit
46 if limit >= 20 and limit <= 40:
46 if limit >= 20 and limit <= 40:
47 yield 50
47 yield 50
48 else:
48 else:
49 yield 1 * factor
49 yield 1 * factor
50 yield 3 * factor
50 yield 3 * factor
51 for f in seq(factor * 10):
51 for f in seq(factor * 10):
52 yield f
52 yield f
53
53
54 def nav(**map):
54 def nav(**map):
55 l = []
55 l = []
56 last = 0
56 last = 0
57 for f in seq(1, pagelen):
57 for f in seq(1, pagelen):
58 if f < pagelen or f <= last:
58 if f < pagelen or f <= last:
59 continue
59 continue
60 if f > limit:
60 if f > limit:
61 break
61 break
62 last = f
62 last = f
63 if pos + f < limit:
63 if pos + f < limit:
64 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
64 l.append(("+%d" % f, hex(nodefunc(pos + f).node())))
65 if pos - f >= 0:
65 if pos - f >= 0:
66 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
66 l.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
67
67
68 try:
68 try:
69 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
69 yield {"label": "(0)", "node": hex(nodefunc('0').node())}
70
70
71 for label, node in l:
71 for label, node in l:
72 yield {"label": label, "node": node}
72 yield {"label": label, "node": node}
73
73
74 yield {"label": "tip", "node": "tip"}
74 yield {"label": "tip", "node": "tip"}
75 except hg.RepoError:
75 except hg.RepoError:
76 pass
76 pass
77
77
78 return nav
78 return nav
79
79
80 class hgweb(object):
80 class hgweb(object):
81 def __init__(self, repo, name=None):
81 def __init__(self, repo, name=None):
82 if isinstance(repo, str):
82 if isinstance(repo, str):
83 parentui = ui.ui(report_untrusted=False, interactive=False)
83 parentui = ui.ui(report_untrusted=False, interactive=False)
84 self.repo = hg.repository(parentui, repo)
84 self.repo = hg.repository(parentui, repo)
85 else:
85 else:
86 self.repo = repo
86 self.repo = repo
87
87
88 hook.redirect(True)
88 self.mtime = -1
89 self.mtime = -1
89 self.reponame = name
90 self.reponame = name
90 self.archives = 'zip', 'gz', 'bz2'
91 self.archives = 'zip', 'gz', 'bz2'
91 self.stripecount = 1
92 self.stripecount = 1
92 # a repo owner may set web.templates in .hg/hgrc to get any file
93 # a repo owner may set web.templates in .hg/hgrc to get any file
93 # readable by the user running the CGI script
94 # readable by the user running the CGI script
94 self.templatepath = self.config("web", "templates",
95 self.templatepath = self.config("web", "templates",
95 templater.templatepath(),
96 templater.templatepath(),
96 untrusted=False)
97 untrusted=False)
97
98
98 # The CGI scripts are often run by a user different from the repo owner.
99 # The CGI scripts are often run by a user different from the repo owner.
99 # Trust the settings from the .hg/hgrc files by default.
100 # Trust the settings from the .hg/hgrc files by default.
100 def config(self, section, name, default=None, untrusted=True):
101 def config(self, section, name, default=None, untrusted=True):
101 return self.repo.ui.config(section, name, default,
102 return self.repo.ui.config(section, name, default,
102 untrusted=untrusted)
103 untrusted=untrusted)
103
104
104 def configbool(self, section, name, default=False, untrusted=True):
105 def configbool(self, section, name, default=False, untrusted=True):
105 return self.repo.ui.configbool(section, name, default,
106 return self.repo.ui.configbool(section, name, default,
106 untrusted=untrusted)
107 untrusted=untrusted)
107
108
108 def configlist(self, section, name, default=None, untrusted=True):
109 def configlist(self, section, name, default=None, untrusted=True):
109 return self.repo.ui.configlist(section, name, default,
110 return self.repo.ui.configlist(section, name, default,
110 untrusted=untrusted)
111 untrusted=untrusted)
111
112
112 def refresh(self):
113 def refresh(self):
113 mtime = get_mtime(self.repo.root)
114 mtime = get_mtime(self.repo.root)
114 if mtime != self.mtime:
115 if mtime != self.mtime:
115 self.mtime = mtime
116 self.mtime = mtime
116 self.repo = hg.repository(self.repo.ui, self.repo.root)
117 self.repo = hg.repository(self.repo.ui, self.repo.root)
117 self.maxchanges = int(self.config("web", "maxchanges", 10))
118 self.maxchanges = int(self.config("web", "maxchanges", 10))
118 self.stripecount = int(self.config("web", "stripes", 1))
119 self.stripecount = int(self.config("web", "stripes", 1))
119 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
120 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
120 self.maxfiles = int(self.config("web", "maxfiles", 10))
121 self.maxfiles = int(self.config("web", "maxfiles", 10))
121 self.allowpull = self.configbool("web", "allowpull", True)
122 self.allowpull = self.configbool("web", "allowpull", True)
122 self.encoding = self.config("web", "encoding", util._encoding)
123 self.encoding = self.config("web", "encoding", util._encoding)
123
124
124 def run(self):
125 def run(self):
125 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
126 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
126 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
127 raise RuntimeError("This function is only intended to be called while running as a CGI script.")
127 import mercurial.hgweb.wsgicgi as wsgicgi
128 import mercurial.hgweb.wsgicgi as wsgicgi
128 wsgicgi.launch(self)
129 wsgicgi.launch(self)
129
130
130 def __call__(self, env, respond):
131 def __call__(self, env, respond):
131 req = wsgirequest(env, respond)
132 req = wsgirequest(env, respond)
132 self.run_wsgi(req)
133 self.run_wsgi(req)
133 return req
134 return req
134
135
135 def run_wsgi(self, req):
136 def run_wsgi(self, req):
136
137
137 self.refresh()
138 self.refresh()
138
139
139 # expand form shortcuts
140 # expand form shortcuts
140
141
141 for k in shortcuts.iterkeys():
142 for k in shortcuts.iterkeys():
142 if k in req.form:
143 if k in req.form:
143 for name, value in shortcuts[k]:
144 for name, value in shortcuts[k]:
144 if value is None:
145 if value is None:
145 value = req.form[k]
146 value = req.form[k]
146 req.form[name] = value
147 req.form[name] = value
147 del req.form[k]
148 del req.form[k]
148
149
149 # work with CGI variables to create coherent structure
150 # work with CGI variables to create coherent structure
150 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
151 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
151
152
152 req.url = req.env['SCRIPT_NAME']
153 req.url = req.env['SCRIPT_NAME']
153 if not req.url.endswith('/'):
154 if not req.url.endswith('/'):
154 req.url += '/'
155 req.url += '/'
155 if req.env.has_key('REPO_NAME'):
156 if req.env.has_key('REPO_NAME'):
156 req.url += req.env['REPO_NAME'] + '/'
157 req.url += req.env['REPO_NAME'] + '/'
157
158
158 if req.env.get('PATH_INFO'):
159 if req.env.get('PATH_INFO'):
159 parts = req.env.get('PATH_INFO').strip('/').split('/')
160 parts = req.env.get('PATH_INFO').strip('/').split('/')
160 repo_parts = req.env.get('REPO_NAME', '').split('/')
161 repo_parts = req.env.get('REPO_NAME', '').split('/')
161 if parts[:len(repo_parts)] == repo_parts:
162 if parts[:len(repo_parts)] == repo_parts:
162 parts = parts[len(repo_parts):]
163 parts = parts[len(repo_parts):]
163 query = '/'.join(parts)
164 query = '/'.join(parts)
164 else:
165 else:
165 query = req.env['QUERY_STRING'].split('&', 1)[0]
166 query = req.env['QUERY_STRING'].split('&', 1)[0]
166 query = query.split(';', 1)[0]
167 query = query.split(';', 1)[0]
167
168
168 # translate user-visible url structure to internal structure
169 # translate user-visible url structure to internal structure
169
170
170 args = query.split('/', 2)
171 args = query.split('/', 2)
171 if 'cmd' not in req.form and args and args[0]:
172 if 'cmd' not in req.form and args and args[0]:
172
173
173 cmd = args.pop(0)
174 cmd = args.pop(0)
174 style = cmd.rfind('-')
175 style = cmd.rfind('-')
175 if style != -1:
176 if style != -1:
176 req.form['style'] = [cmd[:style]]
177 req.form['style'] = [cmd[:style]]
177 cmd = cmd[style+1:]
178 cmd = cmd[style+1:]
178
179
179 # avoid accepting e.g. style parameter as command
180 # avoid accepting e.g. style parameter as command
180 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
181 if hasattr(webcommands, cmd) or hasattr(protocol, cmd):
181 req.form['cmd'] = [cmd]
182 req.form['cmd'] = [cmd]
182
183
183 if args and args[0]:
184 if args and args[0]:
184 node = args.pop(0)
185 node = args.pop(0)
185 req.form['node'] = [node]
186 req.form['node'] = [node]
186 if args:
187 if args:
187 req.form['file'] = args
188 req.form['file'] = args
188
189
189 if cmd == 'static':
190 if cmd == 'static':
190 req.form['file'] = req.form['node']
191 req.form['file'] = req.form['node']
191 elif cmd == 'archive':
192 elif cmd == 'archive':
192 fn = req.form['node'][0]
193 fn = req.form['node'][0]
193 for type_, spec in self.archive_specs.iteritems():
194 for type_, spec in self.archive_specs.iteritems():
194 ext = spec[2]
195 ext = spec[2]
195 if fn.endswith(ext):
196 if fn.endswith(ext):
196 req.form['node'] = [fn[:-len(ext)]]
197 req.form['node'] = [fn[:-len(ext)]]
197 req.form['type'] = [type_]
198 req.form['type'] = [type_]
198
199
199 # actually process the request
200 # actually process the request
200
201
201 try:
202 try:
202
203
203 cmd = req.form.get('cmd', [''])[0]
204 cmd = req.form.get('cmd', [''])[0]
204 if hasattr(protocol, cmd):
205 if hasattr(protocol, cmd):
205 method = getattr(protocol, cmd)
206 method = getattr(protocol, cmd)
206 method(self, req)
207 method(self, req)
207 else:
208 else:
208 tmpl = self.templater(req)
209 tmpl = self.templater(req)
209 if cmd == '':
210 if cmd == '':
210 req.form['cmd'] = [tmpl.cache['default']]
211 req.form['cmd'] = [tmpl.cache['default']]
211 cmd = req.form['cmd'][0]
212 cmd = req.form['cmd'][0]
212 method = getattr(webcommands, cmd)
213 method = getattr(webcommands, cmd)
213 method(self, req, tmpl)
214 method(self, req, tmpl)
214 del tmpl
215 del tmpl
215
216
216 except revlog.LookupError, err:
217 except revlog.LookupError, err:
217 req.respond(404, tmpl(
218 req.respond(404, tmpl(
218 'error', error='revision not found: %s' % err.name))
219 'error', error='revision not found: %s' % err.name))
219 except (hg.RepoError, revlog.RevlogError), inst:
220 except (hg.RepoError, revlog.RevlogError), inst:
220 req.respond('500 Internal Server Error',
221 req.respond('500 Internal Server Error',
221 tmpl('error', error=str(inst)))
222 tmpl('error', error=str(inst)))
222 except ErrorResponse, inst:
223 except ErrorResponse, inst:
223 req.respond(inst.code, tmpl('error', error=inst.message))
224 req.respond(inst.code, tmpl('error', error=inst.message))
224 except AttributeError:
225 except AttributeError:
225 req.respond(400, tmpl('error', error='No such method: ' + cmd))
226 req.respond(400, tmpl('error', error='No such method: ' + cmd))
226
227
227 def templater(self, req):
228 def templater(self, req):
228
229
229 # determine scheme, port and server name
230 # determine scheme, port and server name
230 # this is needed to create absolute urls
231 # this is needed to create absolute urls
231
232
232 proto = req.env.get('wsgi.url_scheme')
233 proto = req.env.get('wsgi.url_scheme')
233 if proto == 'https':
234 if proto == 'https':
234 proto = 'https'
235 proto = 'https'
235 default_port = "443"
236 default_port = "443"
236 else:
237 else:
237 proto = 'http'
238 proto = 'http'
238 default_port = "80"
239 default_port = "80"
239
240
240 port = req.env["SERVER_PORT"]
241 port = req.env["SERVER_PORT"]
241 port = port != default_port and (":" + port) or ""
242 port = port != default_port and (":" + port) or ""
242 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
243 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
243 staticurl = self.config("web", "staticurl") or req.url + 'static/'
244 staticurl = self.config("web", "staticurl") or req.url + 'static/'
244 if not staticurl.endswith('/'):
245 if not staticurl.endswith('/'):
245 staticurl += '/'
246 staticurl += '/'
246
247
247 # some functions for the templater
248 # some functions for the templater
248
249
249 def header(**map):
250 def header(**map):
250 header_file = cStringIO.StringIO(
251 header_file = cStringIO.StringIO(
251 ''.join(tmpl("header", encoding=self.encoding, **map)))
252 ''.join(tmpl("header", encoding=self.encoding, **map)))
252 msg = mimetools.Message(header_file, 0)
253 msg = mimetools.Message(header_file, 0)
253 req.header(msg.items())
254 req.header(msg.items())
254 yield header_file.read()
255 yield header_file.read()
255
256
256 def rawfileheader(**map):
257 def rawfileheader(**map):
257 req.header([('Content-type', map['mimetype']),
258 req.header([('Content-type', map['mimetype']),
258 ('Content-disposition', 'filename=%s' % map['file']),
259 ('Content-disposition', 'filename=%s' % map['file']),
259 ('Content-length', str(len(map['raw'])))])
260 ('Content-length', str(len(map['raw'])))])
260 yield ''
261 yield ''
261
262
262 def footer(**map):
263 def footer(**map):
263 yield tmpl("footer", **map)
264 yield tmpl("footer", **map)
264
265
265 def motd(**map):
266 def motd(**map):
266 yield self.config("web", "motd", "")
267 yield self.config("web", "motd", "")
267
268
268 def sessionvars(**map):
269 def sessionvars(**map):
269 fields = []
270 fields = []
270 if req.form.has_key('style'):
271 if req.form.has_key('style'):
271 style = req.form['style'][0]
272 style = req.form['style'][0]
272 if style != self.config('web', 'style', ''):
273 if style != self.config('web', 'style', ''):
273 fields.append(('style', style))
274 fields.append(('style', style))
274
275
275 separator = req.url[-1] == '?' and ';' or '?'
276 separator = req.url[-1] == '?' and ';' or '?'
276 for name, value in fields:
277 for name, value in fields:
277 yield dict(name=name, value=value, separator=separator)
278 yield dict(name=name, value=value, separator=separator)
278 separator = ';'
279 separator = ';'
279
280
280 # figure out which style to use
281 # figure out which style to use
281
282
282 style = self.config("web", "style", "")
283 style = self.config("web", "style", "")
283 if req.form.has_key('style'):
284 if req.form.has_key('style'):
284 style = req.form['style'][0]
285 style = req.form['style'][0]
285 mapfile = style_map(self.templatepath, style)
286 mapfile = style_map(self.templatepath, style)
286
287
287 if not self.reponame:
288 if not self.reponame:
288 self.reponame = (self.config("web", "name")
289 self.reponame = (self.config("web", "name")
289 or req.env.get('REPO_NAME')
290 or req.env.get('REPO_NAME')
290 or req.url.strip('/') or self.repo.root)
291 or req.url.strip('/') or self.repo.root)
291
292
292 # create the templater
293 # create the templater
293
294
294 tmpl = templater.templater(mapfile, templater.common_filters,
295 tmpl = templater.templater(mapfile, templater.common_filters,
295 defaults={"url": req.url,
296 defaults={"url": req.url,
296 "staticurl": staticurl,
297 "staticurl": staticurl,
297 "urlbase": urlbase,
298 "urlbase": urlbase,
298 "repo": self.reponame,
299 "repo": self.reponame,
299 "header": header,
300 "header": header,
300 "footer": footer,
301 "footer": footer,
301 "motd": motd,
302 "motd": motd,
302 "rawfileheader": rawfileheader,
303 "rawfileheader": rawfileheader,
303 "sessionvars": sessionvars
304 "sessionvars": sessionvars
304 })
305 })
305 return tmpl
306 return tmpl
306
307
307 def archivelist(self, nodeid):
308 def archivelist(self, nodeid):
308 allowed = self.configlist("web", "allow_archive")
309 allowed = self.configlist("web", "allow_archive")
309 for i, spec in self.archive_specs.iteritems():
310 for i, spec in self.archive_specs.iteritems():
310 if i in allowed or self.configbool("web", "allow" + i):
311 if i in allowed or self.configbool("web", "allow" + i):
311 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
312 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
312
313
313 def listfilediffs(self, tmpl, files, changeset):
314 def listfilediffs(self, tmpl, files, changeset):
314 for f in files[:self.maxfiles]:
315 for f in files[:self.maxfiles]:
315 yield tmpl("filedifflink", node=hex(changeset), file=f)
316 yield tmpl("filedifflink", node=hex(changeset), file=f)
316 if len(files) > self.maxfiles:
317 if len(files) > self.maxfiles:
317 yield tmpl("fileellipses")
318 yield tmpl("fileellipses")
318
319
319 def siblings(self, siblings=[], hiderev=None, **args):
320 def siblings(self, siblings=[], hiderev=None, **args):
320 siblings = [s for s in siblings if s.node() != nullid]
321 siblings = [s for s in siblings if s.node() != nullid]
321 if len(siblings) == 1 and siblings[0].rev() == hiderev:
322 if len(siblings) == 1 and siblings[0].rev() == hiderev:
322 return
323 return
323 for s in siblings:
324 for s in siblings:
324 d = {'node': hex(s.node()), 'rev': s.rev()}
325 d = {'node': hex(s.node()), 'rev': s.rev()}
325 if hasattr(s, 'path'):
326 if hasattr(s, 'path'):
326 d['file'] = s.path()
327 d['file'] = s.path()
327 d.update(args)
328 d.update(args)
328 yield d
329 yield d
329
330
330 def renamelink(self, fl, node):
331 def renamelink(self, fl, node):
331 r = fl.renamed(node)
332 r = fl.renamed(node)
332 if r:
333 if r:
333 return [dict(file=r[0], node=hex(r[1]))]
334 return [dict(file=r[0], node=hex(r[1]))]
334 return []
335 return []
335
336
336 def nodetagsdict(self, node):
337 def nodetagsdict(self, node):
337 return [{"name": i} for i in self.repo.nodetags(node)]
338 return [{"name": i} for i in self.repo.nodetags(node)]
338
339
339 def nodebranchdict(self, ctx):
340 def nodebranchdict(self, ctx):
340 branches = []
341 branches = []
341 branch = ctx.branch()
342 branch = ctx.branch()
342 # If this is an empty repo, ctx.node() == nullid,
343 # If this is an empty repo, ctx.node() == nullid,
343 # ctx.branch() == 'default', but branchtags() is
344 # ctx.branch() == 'default', but branchtags() is
344 # an empty dict. Using dict.get avoids a traceback.
345 # an empty dict. Using dict.get avoids a traceback.
345 if self.repo.branchtags().get(branch) == ctx.node():
346 if self.repo.branchtags().get(branch) == ctx.node():
346 branches.append({"name": branch})
347 branches.append({"name": branch})
347 return branches
348 return branches
348
349
349 def showtag(self, tmpl, t1, node=nullid, **args):
350 def showtag(self, tmpl, t1, node=nullid, **args):
350 for t in self.repo.nodetags(node):
351 for t in self.repo.nodetags(node):
351 yield tmpl(t1, tag=t, **args)
352 yield tmpl(t1, tag=t, **args)
352
353
353 def diff(self, tmpl, node1, node2, files):
354 def diff(self, tmpl, node1, node2, files):
354 def filterfiles(filters, files):
355 def filterfiles(filters, files):
355 l = [x for x in files if x in filters]
356 l = [x for x in files if x in filters]
356
357
357 for t in filters:
358 for t in filters:
358 if t and t[-1] != os.sep:
359 if t and t[-1] != os.sep:
359 t += os.sep
360 t += os.sep
360 l += [x for x in files if x.startswith(t)]
361 l += [x for x in files if x.startswith(t)]
361 return l
362 return l
362
363
363 parity = paritygen(self.stripecount)
364 parity = paritygen(self.stripecount)
364 def diffblock(diff, f, fn):
365 def diffblock(diff, f, fn):
365 yield tmpl("diffblock",
366 yield tmpl("diffblock",
366 lines=prettyprintlines(diff),
367 lines=prettyprintlines(diff),
367 parity=parity.next(),
368 parity=parity.next(),
368 file=f,
369 file=f,
369 filenode=hex(fn or nullid))
370 filenode=hex(fn or nullid))
370
371
371 def prettyprintlines(diff):
372 def prettyprintlines(diff):
372 for l in diff.splitlines(1):
373 for l in diff.splitlines(1):
373 if l.startswith('+'):
374 if l.startswith('+'):
374 yield tmpl("difflineplus", line=l)
375 yield tmpl("difflineplus", line=l)
375 elif l.startswith('-'):
376 elif l.startswith('-'):
376 yield tmpl("difflineminus", line=l)
377 yield tmpl("difflineminus", line=l)
377 elif l.startswith('@'):
378 elif l.startswith('@'):
378 yield tmpl("difflineat", line=l)
379 yield tmpl("difflineat", line=l)
379 else:
380 else:
380 yield tmpl("diffline", line=l)
381 yield tmpl("diffline", line=l)
381
382
382 r = self.repo
383 r = self.repo
383 c1 = r.changectx(node1)
384 c1 = r.changectx(node1)
384 c2 = r.changectx(node2)
385 c2 = r.changectx(node2)
385 date1 = util.datestr(c1.date())
386 date1 = util.datestr(c1.date())
386 date2 = util.datestr(c2.date())
387 date2 = util.datestr(c2.date())
387
388
388 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
389 modified, added, removed, deleted, unknown = r.status(node1, node2)[:5]
389 if files:
390 if files:
390 modified, added, removed = map(lambda x: filterfiles(files, x),
391 modified, added, removed = map(lambda x: filterfiles(files, x),
391 (modified, added, removed))
392 (modified, added, removed))
392
393
393 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
394 diffopts = patch.diffopts(self.repo.ui, untrusted=True)
394 for f in modified:
395 for f in modified:
395 to = c1.filectx(f).data()
396 to = c1.filectx(f).data()
396 tn = c2.filectx(f).data()
397 tn = c2.filectx(f).data()
397 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
398 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
398 opts=diffopts), f, tn)
399 opts=diffopts), f, tn)
399 for f in added:
400 for f in added:
400 to = None
401 to = None
401 tn = c2.filectx(f).data()
402 tn = c2.filectx(f).data()
402 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
403 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
403 opts=diffopts), f, tn)
404 opts=diffopts), f, tn)
404 for f in removed:
405 for f in removed:
405 to = c1.filectx(f).data()
406 to = c1.filectx(f).data()
406 tn = None
407 tn = None
407 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
408 yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, f,
408 opts=diffopts), f, tn)
409 opts=diffopts), f, tn)
409
410
410 def changelog(self, tmpl, ctx, shortlog=False):
411 def changelog(self, tmpl, ctx, shortlog=False):
411 def changelist(limit=0,**map):
412 def changelist(limit=0,**map):
412 cl = self.repo.changelog
413 cl = self.repo.changelog
413 l = [] # build a list in forward order for efficiency
414 l = [] # build a list in forward order for efficiency
414 for i in xrange(start, end):
415 for i in xrange(start, end):
415 ctx = self.repo.changectx(i)
416 ctx = self.repo.changectx(i)
416 n = ctx.node()
417 n = ctx.node()
417
418
418 l.insert(0, {"parity": parity.next(),
419 l.insert(0, {"parity": parity.next(),
419 "author": ctx.user(),
420 "author": ctx.user(),
420 "parent": self.siblings(ctx.parents(), i - 1),
421 "parent": self.siblings(ctx.parents(), i - 1),
421 "child": self.siblings(ctx.children(), i + 1),
422 "child": self.siblings(ctx.children(), i + 1),
422 "changelogtag": self.showtag("changelogtag",n),
423 "changelogtag": self.showtag("changelogtag",n),
423 "desc": ctx.description(),
424 "desc": ctx.description(),
424 "date": ctx.date(),
425 "date": ctx.date(),
425 "files": self.listfilediffs(tmpl, ctx.files(), n),
426 "files": self.listfilediffs(tmpl, ctx.files(), n),
426 "rev": i,
427 "rev": i,
427 "node": hex(n),
428 "node": hex(n),
428 "tags": self.nodetagsdict(n),
429 "tags": self.nodetagsdict(n),
429 "branches": self.nodebranchdict(ctx)})
430 "branches": self.nodebranchdict(ctx)})
430
431
431 if limit > 0:
432 if limit > 0:
432 l = l[:limit]
433 l = l[:limit]
433
434
434 for e in l:
435 for e in l:
435 yield e
436 yield e
436
437
437 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
438 maxchanges = shortlog and self.maxshortchanges or self.maxchanges
438 cl = self.repo.changelog
439 cl = self.repo.changelog
439 count = cl.count()
440 count = cl.count()
440 pos = ctx.rev()
441 pos = ctx.rev()
441 start = max(0, pos - maxchanges + 1)
442 start = max(0, pos - maxchanges + 1)
442 end = min(count, start + maxchanges)
443 end = min(count, start + maxchanges)
443 pos = end - 1
444 pos = end - 1
444 parity = paritygen(self.stripecount, offset=start-end)
445 parity = paritygen(self.stripecount, offset=start-end)
445
446
446 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
447 changenav = revnavgen(pos, maxchanges, count, self.repo.changectx)
447
448
448 yield tmpl(shortlog and 'shortlog' or 'changelog',
449 yield tmpl(shortlog and 'shortlog' or 'changelog',
449 changenav=changenav,
450 changenav=changenav,
450 node=hex(cl.tip()),
451 node=hex(cl.tip()),
451 rev=pos, changesets=count,
452 rev=pos, changesets=count,
452 entries=lambda **x: changelist(limit=0,**x),
453 entries=lambda **x: changelist(limit=0,**x),
453 latestentry=lambda **x: changelist(limit=1,**x),
454 latestentry=lambda **x: changelist(limit=1,**x),
454 archives=self.archivelist("tip"))
455 archives=self.archivelist("tip"))
455
456
456 def search(self, tmpl, query):
457 def search(self, tmpl, query):
457
458
458 def changelist(**map):
459 def changelist(**map):
459 cl = self.repo.changelog
460 cl = self.repo.changelog
460 count = 0
461 count = 0
461 qw = query.lower().split()
462 qw = query.lower().split()
462
463
463 def revgen():
464 def revgen():
464 for i in xrange(cl.count() - 1, 0, -100):
465 for i in xrange(cl.count() - 1, 0, -100):
465 l = []
466 l = []
466 for j in xrange(max(0, i - 100), i):
467 for j in xrange(max(0, i - 100), i):
467 ctx = self.repo.changectx(j)
468 ctx = self.repo.changectx(j)
468 l.append(ctx)
469 l.append(ctx)
469 l.reverse()
470 l.reverse()
470 for e in l:
471 for e in l:
471 yield e
472 yield e
472
473
473 for ctx in revgen():
474 for ctx in revgen():
474 miss = 0
475 miss = 0
475 for q in qw:
476 for q in qw:
476 if not (q in ctx.user().lower() or
477 if not (q in ctx.user().lower() or
477 q in ctx.description().lower() or
478 q in ctx.description().lower() or
478 q in " ".join(ctx.files()).lower()):
479 q in " ".join(ctx.files()).lower()):
479 miss = 1
480 miss = 1
480 break
481 break
481 if miss:
482 if miss:
482 continue
483 continue
483
484
484 count += 1
485 count += 1
485 n = ctx.node()
486 n = ctx.node()
486
487
487 yield tmpl('searchentry',
488 yield tmpl('searchentry',
488 parity=parity.next(),
489 parity=parity.next(),
489 author=ctx.user(),
490 author=ctx.user(),
490 parent=self.siblings(ctx.parents()),
491 parent=self.siblings(ctx.parents()),
491 child=self.siblings(ctx.children()),
492 child=self.siblings(ctx.children()),
492 changelogtag=self.showtag("changelogtag",n),
493 changelogtag=self.showtag("changelogtag",n),
493 desc=ctx.description(),
494 desc=ctx.description(),
494 date=ctx.date(),
495 date=ctx.date(),
495 files=self.listfilediffs(tmpl, ctx.files(), n),
496 files=self.listfilediffs(tmpl, ctx.files(), n),
496 rev=ctx.rev(),
497 rev=ctx.rev(),
497 node=hex(n),
498 node=hex(n),
498 tags=self.nodetagsdict(n),
499 tags=self.nodetagsdict(n),
499 branches=self.nodebranchdict(ctx))
500 branches=self.nodebranchdict(ctx))
500
501
501 if count >= self.maxchanges:
502 if count >= self.maxchanges:
502 break
503 break
503
504
504 cl = self.repo.changelog
505 cl = self.repo.changelog
505 parity = paritygen(self.stripecount)
506 parity = paritygen(self.stripecount)
506
507
507 yield tmpl('search',
508 yield tmpl('search',
508 query=query,
509 query=query,
509 node=hex(cl.tip()),
510 node=hex(cl.tip()),
510 entries=changelist,
511 entries=changelist,
511 archives=self.archivelist("tip"))
512 archives=self.archivelist("tip"))
512
513
513 def changeset(self, tmpl, ctx):
514 def changeset(self, tmpl, ctx):
514 n = ctx.node()
515 n = ctx.node()
515 parents = ctx.parents()
516 parents = ctx.parents()
516 p1 = parents[0].node()
517 p1 = parents[0].node()
517
518
518 files = []
519 files = []
519 parity = paritygen(self.stripecount)
520 parity = paritygen(self.stripecount)
520 for f in ctx.files():
521 for f in ctx.files():
521 files.append(tmpl("filenodelink",
522 files.append(tmpl("filenodelink",
522 node=hex(n), file=f,
523 node=hex(n), file=f,
523 parity=parity.next()))
524 parity=parity.next()))
524
525
525 def diff(**map):
526 def diff(**map):
526 yield self.diff(tmpl, p1, n, None)
527 yield self.diff(tmpl, p1, n, None)
527
528
528 yield tmpl('changeset',
529 yield tmpl('changeset',
529 diff=diff,
530 diff=diff,
530 rev=ctx.rev(),
531 rev=ctx.rev(),
531 node=hex(n),
532 node=hex(n),
532 parent=self.siblings(parents),
533 parent=self.siblings(parents),
533 child=self.siblings(ctx.children()),
534 child=self.siblings(ctx.children()),
534 changesettag=self.showtag("changesettag",n),
535 changesettag=self.showtag("changesettag",n),
535 author=ctx.user(),
536 author=ctx.user(),
536 desc=ctx.description(),
537 desc=ctx.description(),
537 date=ctx.date(),
538 date=ctx.date(),
538 files=files,
539 files=files,
539 archives=self.archivelist(hex(n)),
540 archives=self.archivelist(hex(n)),
540 tags=self.nodetagsdict(n),
541 tags=self.nodetagsdict(n),
541 branches=self.nodebranchdict(ctx))
542 branches=self.nodebranchdict(ctx))
542
543
543 def filelog(self, tmpl, fctx):
544 def filelog(self, tmpl, fctx):
544 f = fctx.path()
545 f = fctx.path()
545 fl = fctx.filelog()
546 fl = fctx.filelog()
546 count = fl.count()
547 count = fl.count()
547 pagelen = self.maxshortchanges
548 pagelen = self.maxshortchanges
548 pos = fctx.filerev()
549 pos = fctx.filerev()
549 start = max(0, pos - pagelen + 1)
550 start = max(0, pos - pagelen + 1)
550 end = min(count, start + pagelen)
551 end = min(count, start + pagelen)
551 pos = end - 1
552 pos = end - 1
552 parity = paritygen(self.stripecount, offset=start-end)
553 parity = paritygen(self.stripecount, offset=start-end)
553
554
554 def entries(limit=0, **map):
555 def entries(limit=0, **map):
555 l = []
556 l = []
556
557
557 for i in xrange(start, end):
558 for i in xrange(start, end):
558 ctx = fctx.filectx(i)
559 ctx = fctx.filectx(i)
559 n = fl.node(i)
560 n = fl.node(i)
560
561
561 l.insert(0, {"parity": parity.next(),
562 l.insert(0, {"parity": parity.next(),
562 "filerev": i,
563 "filerev": i,
563 "file": f,
564 "file": f,
564 "node": hex(ctx.node()),
565 "node": hex(ctx.node()),
565 "author": ctx.user(),
566 "author": ctx.user(),
566 "date": ctx.date(),
567 "date": ctx.date(),
567 "rename": self.renamelink(fl, n),
568 "rename": self.renamelink(fl, n),
568 "parent": self.siblings(fctx.parents()),
569 "parent": self.siblings(fctx.parents()),
569 "child": self.siblings(fctx.children()),
570 "child": self.siblings(fctx.children()),
570 "desc": ctx.description()})
571 "desc": ctx.description()})
571
572
572 if limit > 0:
573 if limit > 0:
573 l = l[:limit]
574 l = l[:limit]
574
575
575 for e in l:
576 for e in l:
576 yield e
577 yield e
577
578
578 nodefunc = lambda x: fctx.filectx(fileid=x)
579 nodefunc = lambda x: fctx.filectx(fileid=x)
579 nav = revnavgen(pos, pagelen, count, nodefunc)
580 nav = revnavgen(pos, pagelen, count, nodefunc)
580 yield tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
581 yield tmpl("filelog", file=f, node=hex(fctx.node()), nav=nav,
581 entries=lambda **x: entries(limit=0, **x),
582 entries=lambda **x: entries(limit=0, **x),
582 latestentry=lambda **x: entries(limit=1, **x))
583 latestentry=lambda **x: entries(limit=1, **x))
583
584
584 def filerevision(self, tmpl, fctx):
585 def filerevision(self, tmpl, fctx):
585 f = fctx.path()
586 f = fctx.path()
586 text = fctx.data()
587 text = fctx.data()
587 fl = fctx.filelog()
588 fl = fctx.filelog()
588 n = fctx.filenode()
589 n = fctx.filenode()
589 parity = paritygen(self.stripecount)
590 parity = paritygen(self.stripecount)
590
591
591 mt = mimetypes.guess_type(f)[0]
592 mt = mimetypes.guess_type(f)[0]
592 rawtext = text
593 rawtext = text
593 if util.binary(text):
594 if util.binary(text):
594 mt = mt or 'application/octet-stream'
595 mt = mt or 'application/octet-stream'
595 text = "(binary:%s)" % mt
596 text = "(binary:%s)" % mt
596 mt = mt or 'text/plain'
597 mt = mt or 'text/plain'
597
598
598 def lines():
599 def lines():
599 for l, t in enumerate(text.splitlines(1)):
600 for l, t in enumerate(text.splitlines(1)):
600 yield {"line": t,
601 yield {"line": t,
601 "linenumber": "% 6d" % (l + 1),
602 "linenumber": "% 6d" % (l + 1),
602 "parity": parity.next()}
603 "parity": parity.next()}
603
604
604 yield tmpl("filerevision",
605 yield tmpl("filerevision",
605 file=f,
606 file=f,
606 path=_up(f),
607 path=_up(f),
607 text=lines(),
608 text=lines(),
608 raw=rawtext,
609 raw=rawtext,
609 mimetype=mt,
610 mimetype=mt,
610 rev=fctx.rev(),
611 rev=fctx.rev(),
611 node=hex(fctx.node()),
612 node=hex(fctx.node()),
612 author=fctx.user(),
613 author=fctx.user(),
613 date=fctx.date(),
614 date=fctx.date(),
614 desc=fctx.description(),
615 desc=fctx.description(),
615 parent=self.siblings(fctx.parents()),
616 parent=self.siblings(fctx.parents()),
616 child=self.siblings(fctx.children()),
617 child=self.siblings(fctx.children()),
617 rename=self.renamelink(fl, n),
618 rename=self.renamelink(fl, n),
618 permissions=fctx.manifest().flags(f))
619 permissions=fctx.manifest().flags(f))
619
620
620 def fileannotate(self, tmpl, fctx):
621 def fileannotate(self, tmpl, fctx):
621 f = fctx.path()
622 f = fctx.path()
622 n = fctx.filenode()
623 n = fctx.filenode()
623 fl = fctx.filelog()
624 fl = fctx.filelog()
624 parity = paritygen(self.stripecount)
625 parity = paritygen(self.stripecount)
625
626
626 def annotate(**map):
627 def annotate(**map):
627 last = None
628 last = None
628 for f, l in fctx.annotate(follow=True):
629 for f, l in fctx.annotate(follow=True):
629 fnode = f.filenode()
630 fnode = f.filenode()
630 name = self.repo.ui.shortuser(f.user())
631 name = self.repo.ui.shortuser(f.user())
631
632
632 if last != fnode:
633 if last != fnode:
633 last = fnode
634 last = fnode
634
635
635 yield {"parity": parity.next(),
636 yield {"parity": parity.next(),
636 "node": hex(f.node()),
637 "node": hex(f.node()),
637 "rev": f.rev(),
638 "rev": f.rev(),
638 "author": name,
639 "author": name,
639 "file": f.path(),
640 "file": f.path(),
640 "line": l}
641 "line": l}
641
642
642 yield tmpl("fileannotate",
643 yield tmpl("fileannotate",
643 file=f,
644 file=f,
644 annotate=annotate,
645 annotate=annotate,
645 path=_up(f),
646 path=_up(f),
646 rev=fctx.rev(),
647 rev=fctx.rev(),
647 node=hex(fctx.node()),
648 node=hex(fctx.node()),
648 author=fctx.user(),
649 author=fctx.user(),
649 date=fctx.date(),
650 date=fctx.date(),
650 desc=fctx.description(),
651 desc=fctx.description(),
651 rename=self.renamelink(fl, n),
652 rename=self.renamelink(fl, n),
652 parent=self.siblings(fctx.parents()),
653 parent=self.siblings(fctx.parents()),
653 child=self.siblings(fctx.children()),
654 child=self.siblings(fctx.children()),
654 permissions=fctx.manifest().flags(f))
655 permissions=fctx.manifest().flags(f))
655
656
656 def manifest(self, tmpl, ctx, path):
657 def manifest(self, tmpl, ctx, path):
657 mf = ctx.manifest()
658 mf = ctx.manifest()
658 node = ctx.node()
659 node = ctx.node()
659
660
660 files = {}
661 files = {}
661 parity = paritygen(self.stripecount)
662 parity = paritygen(self.stripecount)
662
663
663 if path and path[-1] != "/":
664 if path and path[-1] != "/":
664 path += "/"
665 path += "/"
665 l = len(path)
666 l = len(path)
666 abspath = "/" + path
667 abspath = "/" + path
667
668
668 for f, n in mf.items():
669 for f, n in mf.items():
669 if f[:l] != path:
670 if f[:l] != path:
670 continue
671 continue
671 remain = f[l:]
672 remain = f[l:]
672 if "/" in remain:
673 if "/" in remain:
673 short = remain[:remain.index("/") + 1] # bleah
674 short = remain[:remain.index("/") + 1] # bleah
674 files[short] = (f, None)
675 files[short] = (f, None)
675 else:
676 else:
676 short = os.path.basename(remain)
677 short = os.path.basename(remain)
677 files[short] = (f, n)
678 files[short] = (f, n)
678
679
679 if not files:
680 if not files:
680 raise ErrorResponse(404, 'Path not found: ' + path)
681 raise ErrorResponse(404, 'Path not found: ' + path)
681
682
682 def filelist(**map):
683 def filelist(**map):
683 fl = files.keys()
684 fl = files.keys()
684 fl.sort()
685 fl.sort()
685 for f in fl:
686 for f in fl:
686 full, fnode = files[f]
687 full, fnode = files[f]
687 if not fnode:
688 if not fnode:
688 continue
689 continue
689
690
690 fctx = ctx.filectx(full)
691 fctx = ctx.filectx(full)
691 yield {"file": full,
692 yield {"file": full,
692 "parity": parity.next(),
693 "parity": parity.next(),
693 "basename": f,
694 "basename": f,
694 "date": fctx.changectx().date(),
695 "date": fctx.changectx().date(),
695 "size": fctx.size(),
696 "size": fctx.size(),
696 "permissions": mf.flags(full)}
697 "permissions": mf.flags(full)}
697
698
698 def dirlist(**map):
699 def dirlist(**map):
699 fl = files.keys()
700 fl = files.keys()
700 fl.sort()
701 fl.sort()
701 for f in fl:
702 for f in fl:
702 full, fnode = files[f]
703 full, fnode = files[f]
703 if fnode:
704 if fnode:
704 continue
705 continue
705
706
706 yield {"parity": parity.next(),
707 yield {"parity": parity.next(),
707 "path": "%s%s" % (abspath, f),
708 "path": "%s%s" % (abspath, f),
708 "basename": f[:-1]}
709 "basename": f[:-1]}
709
710
710 yield tmpl("manifest",
711 yield tmpl("manifest",
711 rev=ctx.rev(),
712 rev=ctx.rev(),
712 node=hex(node),
713 node=hex(node),
713 path=abspath,
714 path=abspath,
714 up=_up(abspath),
715 up=_up(abspath),
715 upparity=parity.next(),
716 upparity=parity.next(),
716 fentries=filelist,
717 fentries=filelist,
717 dentries=dirlist,
718 dentries=dirlist,
718 archives=self.archivelist(hex(node)),
719 archives=self.archivelist(hex(node)),
719 tags=self.nodetagsdict(node),
720 tags=self.nodetagsdict(node),
720 branches=self.nodebranchdict(ctx))
721 branches=self.nodebranchdict(ctx))
721
722
722 def tags(self, tmpl):
723 def tags(self, tmpl):
723 i = self.repo.tagslist()
724 i = self.repo.tagslist()
724 i.reverse()
725 i.reverse()
725 parity = paritygen(self.stripecount)
726 parity = paritygen(self.stripecount)
726
727
727 def entries(notip=False,limit=0, **map):
728 def entries(notip=False,limit=0, **map):
728 count = 0
729 count = 0
729 for k, n in i:
730 for k, n in i:
730 if notip and k == "tip":
731 if notip and k == "tip":
731 continue
732 continue
732 if limit > 0 and count >= limit:
733 if limit > 0 and count >= limit:
733 continue
734 continue
734 count = count + 1
735 count = count + 1
735 yield {"parity": parity.next(),
736 yield {"parity": parity.next(),
736 "tag": k,
737 "tag": k,
737 "date": self.repo.changectx(n).date(),
738 "date": self.repo.changectx(n).date(),
738 "node": hex(n)}
739 "node": hex(n)}
739
740
740 yield tmpl("tags",
741 yield tmpl("tags",
741 node=hex(self.repo.changelog.tip()),
742 node=hex(self.repo.changelog.tip()),
742 entries=lambda **x: entries(False,0, **x),
743 entries=lambda **x: entries(False,0, **x),
743 entriesnotip=lambda **x: entries(True,0, **x),
744 entriesnotip=lambda **x: entries(True,0, **x),
744 latestentry=lambda **x: entries(True,1, **x))
745 latestentry=lambda **x: entries(True,1, **x))
745
746
746 def summary(self, tmpl):
747 def summary(self, tmpl):
747 i = self.repo.tagslist()
748 i = self.repo.tagslist()
748 i.reverse()
749 i.reverse()
749
750
750 def tagentries(**map):
751 def tagentries(**map):
751 parity = paritygen(self.stripecount)
752 parity = paritygen(self.stripecount)
752 count = 0
753 count = 0
753 for k, n in i:
754 for k, n in i:
754 if k == "tip": # skip tip
755 if k == "tip": # skip tip
755 continue;
756 continue;
756
757
757 count += 1
758 count += 1
758 if count > 10: # limit to 10 tags
759 if count > 10: # limit to 10 tags
759 break;
760 break;
760
761
761 yield tmpl("tagentry",
762 yield tmpl("tagentry",
762 parity=parity.next(),
763 parity=parity.next(),
763 tag=k,
764 tag=k,
764 node=hex(n),
765 node=hex(n),
765 date=self.repo.changectx(n).date())
766 date=self.repo.changectx(n).date())
766
767
767
768
768 def branches(**map):
769 def branches(**map):
769 parity = paritygen(self.stripecount)
770 parity = paritygen(self.stripecount)
770
771
771 b = self.repo.branchtags()
772 b = self.repo.branchtags()
772 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
773 l = [(-self.repo.changelog.rev(n), n, t) for t, n in b.items()]
773 l.sort()
774 l.sort()
774
775
775 for r,n,t in l:
776 for r,n,t in l:
776 ctx = self.repo.changectx(n)
777 ctx = self.repo.changectx(n)
777
778
778 yield {'parity': parity.next(),
779 yield {'parity': parity.next(),
779 'branch': t,
780 'branch': t,
780 'node': hex(n),
781 'node': hex(n),
781 'date': ctx.date()}
782 'date': ctx.date()}
782
783
783 def changelist(**map):
784 def changelist(**map):
784 parity = paritygen(self.stripecount, offset=start-end)
785 parity = paritygen(self.stripecount, offset=start-end)
785 l = [] # build a list in forward order for efficiency
786 l = [] # build a list in forward order for efficiency
786 for i in xrange(start, end):
787 for i in xrange(start, end):
787 ctx = self.repo.changectx(i)
788 ctx = self.repo.changectx(i)
788 n = ctx.node()
789 n = ctx.node()
789 hn = hex(n)
790 hn = hex(n)
790
791
791 l.insert(0, tmpl(
792 l.insert(0, tmpl(
792 'shortlogentry',
793 'shortlogentry',
793 parity=parity.next(),
794 parity=parity.next(),
794 author=ctx.user(),
795 author=ctx.user(),
795 desc=ctx.description(),
796 desc=ctx.description(),
796 date=ctx.date(),
797 date=ctx.date(),
797 rev=i,
798 rev=i,
798 node=hn,
799 node=hn,
799 tags=self.nodetagsdict(n),
800 tags=self.nodetagsdict(n),
800 branches=self.nodebranchdict(ctx)))
801 branches=self.nodebranchdict(ctx)))
801
802
802 yield l
803 yield l
803
804
804 cl = self.repo.changelog
805 cl = self.repo.changelog
805 count = cl.count()
806 count = cl.count()
806 start = max(0, count - self.maxchanges)
807 start = max(0, count - self.maxchanges)
807 end = min(count, start + self.maxchanges)
808 end = min(count, start + self.maxchanges)
808
809
809 yield tmpl("summary",
810 yield tmpl("summary",
810 desc=self.config("web", "description", "unknown"),
811 desc=self.config("web", "description", "unknown"),
811 owner=get_contact(self.config) or "unknown",
812 owner=get_contact(self.config) or "unknown",
812 lastchange=cl.read(cl.tip())[2],
813 lastchange=cl.read(cl.tip())[2],
813 tags=tagentries,
814 tags=tagentries,
814 branches=branches,
815 branches=branches,
815 shortlog=changelist,
816 shortlog=changelist,
816 node=hex(cl.tip()),
817 node=hex(cl.tip()),
817 archives=self.archivelist("tip"))
818 archives=self.archivelist("tip"))
818
819
819 def filediff(self, tmpl, fctx):
820 def filediff(self, tmpl, fctx):
820 n = fctx.node()
821 n = fctx.node()
821 path = fctx.path()
822 path = fctx.path()
822 parents = fctx.parents()
823 parents = fctx.parents()
823 p1 = parents and parents[0].node() or nullid
824 p1 = parents and parents[0].node() or nullid
824
825
825 def diff(**map):
826 def diff(**map):
826 yield self.diff(tmpl, p1, n, [path])
827 yield self.diff(tmpl, p1, n, [path])
827
828
828 yield tmpl("filediff",
829 yield tmpl("filediff",
829 file=path,
830 file=path,
830 node=hex(n),
831 node=hex(n),
831 rev=fctx.rev(),
832 rev=fctx.rev(),
832 parent=self.siblings(parents),
833 parent=self.siblings(parents),
833 child=self.siblings(fctx.children()),
834 child=self.siblings(fctx.children()),
834 diff=diff)
835 diff=diff)
835
836
836 archive_specs = {
837 archive_specs = {
837 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
838 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
838 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
839 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
839 'zip': ('application/zip', 'zip', '.zip', None),
840 'zip': ('application/zip', 'zip', '.zip', None),
840 }
841 }
841
842
842 def archive(self, tmpl, req, key, type_):
843 def archive(self, tmpl, req, key, type_):
843 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
844 reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame))
844 cnode = self.repo.lookup(key)
845 cnode = self.repo.lookup(key)
845 arch_version = key
846 arch_version = key
846 if cnode == key or key == 'tip':
847 if cnode == key or key == 'tip':
847 arch_version = short(cnode)
848 arch_version = short(cnode)
848 name = "%s-%s" % (reponame, arch_version)
849 name = "%s-%s" % (reponame, arch_version)
849 mimetype, artype, extension, encoding = self.archive_specs[type_]
850 mimetype, artype, extension, encoding = self.archive_specs[type_]
850 headers = [('Content-type', mimetype),
851 headers = [('Content-type', mimetype),
851 ('Content-disposition', 'attachment; filename=%s%s' %
852 ('Content-disposition', 'attachment; filename=%s%s' %
852 (name, extension))]
853 (name, extension))]
853 if encoding:
854 if encoding:
854 headers.append(('Content-encoding', encoding))
855 headers.append(('Content-encoding', encoding))
855 req.header(headers)
856 req.header(headers)
856 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
857 archival.archive(self.repo, req.out, cnode, artype, prefix=name)
857
858
858 # add tags to things
859 # add tags to things
859 # tags -> list of changesets corresponding to tags
860 # tags -> list of changesets corresponding to tags
860 # find tag, changeset, file
861 # find tag, changeset, file
861
862
862 def cleanpath(self, path):
863 def cleanpath(self, path):
863 path = path.lstrip('/')
864 path = path.lstrip('/')
864 return util.canonpath(self.repo.root, '', path)
865 return util.canonpath(self.repo.root, '', path)
865
866
866 def changectx(self, req):
867 def changectx(self, req):
867 if req.form.has_key('node'):
868 if req.form.has_key('node'):
868 changeid = req.form['node'][0]
869 changeid = req.form['node'][0]
869 elif req.form.has_key('manifest'):
870 elif req.form.has_key('manifest'):
870 changeid = req.form['manifest'][0]
871 changeid = req.form['manifest'][0]
871 else:
872 else:
872 changeid = self.repo.changelog.count() - 1
873 changeid = self.repo.changelog.count() - 1
873
874
874 try:
875 try:
875 ctx = self.repo.changectx(changeid)
876 ctx = self.repo.changectx(changeid)
876 except hg.RepoError:
877 except hg.RepoError:
877 man = self.repo.manifest
878 man = self.repo.manifest
878 mn = man.lookup(changeid)
879 mn = man.lookup(changeid)
879 ctx = self.repo.changectx(man.linkrev(mn))
880 ctx = self.repo.changectx(man.linkrev(mn))
880
881
881 return ctx
882 return ctx
882
883
883 def filectx(self, req):
884 def filectx(self, req):
884 path = self.cleanpath(req.form['file'][0])
885 path = self.cleanpath(req.form['file'][0])
885 if req.form.has_key('node'):
886 if req.form.has_key('node'):
886 changeid = req.form['node'][0]
887 changeid = req.form['node'][0]
887 else:
888 else:
888 changeid = req.form['filenode'][0]
889 changeid = req.form['filenode'][0]
889 try:
890 try:
890 ctx = self.repo.changectx(changeid)
891 ctx = self.repo.changectx(changeid)
891 fctx = ctx.filectx(path)
892 fctx = ctx.filectx(path)
892 except hg.RepoError:
893 except hg.RepoError:
893 fctx = self.repo.filectx(path, fileid=changeid)
894 fctx = self.repo.filectx(path, fileid=changeid)
894
895
895 return fctx
896 return fctx
896
897
897 def check_perm(self, req, op, default):
898 def check_perm(self, req, op, default):
898 '''check permission for operation based on user auth.
899 '''check permission for operation based on user auth.
899 return true if op allowed, else false.
900 return true if op allowed, else false.
900 default is policy to use if no config given.'''
901 default is policy to use if no config given.'''
901
902
902 user = req.env.get('REMOTE_USER')
903 user = req.env.get('REMOTE_USER')
903
904
904 deny = self.configlist('web', 'deny_' + op)
905 deny = self.configlist('web', 'deny_' + op)
905 if deny and (not user or deny == ['*'] or user in deny):
906 if deny and (not user or deny == ['*'] or user in deny):
906 return False
907 return False
907
908
908 allow = self.configlist('web', 'allow_' + op)
909 allow = self.configlist('web', 'allow_' + op)
909 return (allow and (allow == ['*'] or user in allow)) or default
910 return (allow and (allow == ['*'] or user in allow)) or default
@@ -1,96 +1,109
1 # hook.py - hook support for mercurial
1 # hook.py - hook support for mercurial
2 #
2 #
3 # Copyright 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from i18n import _
8 from i18n import _
9 import util
9 import util, os, sys
10
10
11 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
11 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
12 '''call python hook. hook is callable object, looked up as
12 '''call python hook. hook is callable object, looked up as
13 name in python module. if callable returns "true", hook
13 name in python module. if callable returns "true", hook
14 fails, else passes. if hook raises exception, treated as
14 fails, else passes. if hook raises exception, treated as
15 hook failure. exception propagates if throw is "true".
15 hook failure. exception propagates if throw is "true".
16
16
17 reason for "true" meaning "hook failed" is so that
17 reason for "true" meaning "hook failed" is so that
18 unmodified commands (e.g. mercurial.commands.update) can
18 unmodified commands (e.g. mercurial.commands.update) can
19 be run as hooks without wrappers to convert return values.'''
19 be run as hooks without wrappers to convert return values.'''
20
20
21 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
21 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
22 obj = funcname
22 obj = funcname
23 if not callable(obj):
23 if not callable(obj):
24 d = funcname.rfind('.')
24 d = funcname.rfind('.')
25 if d == -1:
25 if d == -1:
26 raise util.Abort(_('%s hook is invalid ("%s" not in '
26 raise util.Abort(_('%s hook is invalid ("%s" not in '
27 'a module)') % (hname, funcname))
27 'a module)') % (hname, funcname))
28 modname = funcname[:d]
28 modname = funcname[:d]
29 try:
29 try:
30 obj = __import__(modname)
30 obj = __import__(modname)
31 except ImportError:
31 except ImportError:
32 try:
32 try:
33 # extensions are loaded with hgext_ prefix
33 # extensions are loaded with hgext_ prefix
34 obj = __import__("hgext_%s" % modname)
34 obj = __import__("hgext_%s" % modname)
35 except ImportError:
35 except ImportError:
36 raise util.Abort(_('%s hook is invalid '
36 raise util.Abort(_('%s hook is invalid '
37 '(import of "%s" failed)') %
37 '(import of "%s" failed)') %
38 (hname, modname))
38 (hname, modname))
39 try:
39 try:
40 for p in funcname.split('.')[1:]:
40 for p in funcname.split('.')[1:]:
41 obj = getattr(obj, p)
41 obj = getattr(obj, p)
42 except AttributeError, err:
42 except AttributeError, err:
43 raise util.Abort(_('%s hook is invalid '
43 raise util.Abort(_('%s hook is invalid '
44 '("%s" is not defined)') %
44 '("%s" is not defined)') %
45 (hname, funcname))
45 (hname, funcname))
46 if not callable(obj):
46 if not callable(obj):
47 raise util.Abort(_('%s hook is invalid '
47 raise util.Abort(_('%s hook is invalid '
48 '("%s" is not callable)') %
48 '("%s" is not callable)') %
49 (hname, funcname))
49 (hname, funcname))
50 try:
50 try:
51 r = obj(ui=ui, repo=repo, hooktype=name, **args)
51 r = obj(ui=ui, repo=repo, hooktype=name, **args)
52 except (KeyboardInterrupt, util.SignalInterrupt):
52 except (KeyboardInterrupt, util.SignalInterrupt):
53 raise
53 raise
54 except Exception, exc:
54 except Exception, exc:
55 if isinstance(exc, util.Abort):
55 if isinstance(exc, util.Abort):
56 ui.warn(_('error: %s hook failed: %s\n') %
56 ui.warn(_('error: %s hook failed: %s\n') %
57 (hname, exc.args[0]))
57 (hname, exc.args[0]))
58 else:
58 else:
59 ui.warn(_('error: %s hook raised an exception: '
59 ui.warn(_('error: %s hook raised an exception: '
60 '%s\n') % (hname, exc))
60 '%s\n') % (hname, exc))
61 if throw:
61 if throw:
62 raise
62 raise
63 ui.print_exc()
63 ui.print_exc()
64 return True
64 return True
65 if r:
65 if r:
66 if throw:
66 if throw:
67 raise util.Abort(_('%s hook failed') % hname)
67 raise util.Abort(_('%s hook failed') % hname)
68 ui.warn(_('warning: %s hook failed\n') % hname)
68 ui.warn(_('warning: %s hook failed\n') % hname)
69 return r
69 return r
70
70
71 def _exthook(ui, repo, name, cmd, args, throw):
71 def _exthook(ui, repo, name, cmd, args, throw):
72 ui.note(_("running hook %s: %s\n") % (name, cmd))
72 ui.note(_("running hook %s: %s\n") % (name, cmd))
73 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
73 env = dict([('HG_' + k.upper(), v) for k, v in args.iteritems()])
74 r = util.system(cmd, environ=env, cwd=repo.root)
74 r = util.system(cmd, environ=env, cwd=repo.root)
75 if r:
75 if r:
76 desc, r = util.explain_exit(r)
76 desc, r = util.explain_exit(r)
77 if throw:
77 if throw:
78 raise util.Abort(_('%s hook %s') % (name, desc))
78 raise util.Abort(_('%s hook %s') % (name, desc))
79 ui.warn(_('warning: %s hook %s\n') % (name, desc))
79 ui.warn(_('warning: %s hook %s\n') % (name, desc))
80 return r
80 return r
81
81
82 _redirect = False
83 def redirect(state):
84 _redirect = state
85
82 def hook(ui, repo, name, throw=False, **args):
86 def hook(ui, repo, name, throw=False, **args):
83 r = False
87 r = False
88
89 if _redirect:
90 # temporarily redirect stdout to stderr
91 oldstdout = os.dup(sys.stdout.fileno())
92 os.dup2(sys.stderr.fileno(), sys.stdout.fileno())
93
84 hooks = [(hname, cmd) for hname, cmd in ui.configitems("hooks")
94 hooks = [(hname, cmd) for hname, cmd in ui.configitems("hooks")
85 if hname.split(".", 1)[0] == name and cmd]
95 if hname.split(".", 1)[0] == name and cmd]
86 hooks.sort()
96 hooks.sort()
87 for hname, cmd in hooks:
97 for hname, cmd in hooks:
88 if callable(cmd):
98 if callable(cmd):
89 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
99 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
90 elif cmd.startswith('python:'):
100 elif cmd.startswith('python:'):
91 r = _pythonhook(ui, repo, name, hname, cmd[7:].strip(),
101 r = _pythonhook(ui, repo, name, hname, cmd[7:].strip(),
92 args, throw) or r
102 args, throw) or r
93 else:
103 else:
94 r = _exthook(ui, repo, hname, cmd, args, throw) or r
104 r = _exthook(ui, repo, hname, cmd, args, throw) or r
95 return r
105 return r
96
106
107 if _redirect:
108 os.dup2(oldstdout, sys.stdout.fileno())
109 os.close(oldstdout)
@@ -1,204 +1,205
1 # sshserver.py - ssh protocol server support for mercurial
1 # sshserver.py - ssh protocol server support for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 #
5 #
6 # This software may be used and distributed according to the terms
6 # This software may be used and distributed according to the terms
7 # of the GNU General Public License, incorporated herein by reference.
7 # of the GNU General Public License, incorporated herein by reference.
8
8
9 from i18n import _
9 from i18n import _
10 from node import *
10 from node import *
11 import os, streamclone, sys, tempfile, util
11 import os, streamclone, sys, tempfile, util, hook
12
12
13 class sshserver(object):
13 class sshserver(object):
14 def __init__(self, ui, repo):
14 def __init__(self, ui, repo):
15 self.ui = ui
15 self.ui = ui
16 self.repo = repo
16 self.repo = repo
17 self.lock = None
17 self.lock = None
18 self.fin = sys.stdin
18 self.fin = sys.stdin
19 self.fout = sys.stdout
19 self.fout = sys.stdout
20
20
21 hook.redirect(True)
21 sys.stdout = sys.stderr
22 sys.stdout = sys.stderr
22
23
23 # Prevent insertion/deletion of CRs
24 # Prevent insertion/deletion of CRs
24 util.set_binary(self.fin)
25 util.set_binary(self.fin)
25 util.set_binary(self.fout)
26 util.set_binary(self.fout)
26
27
27 def getarg(self):
28 def getarg(self):
28 argline = self.fin.readline()[:-1]
29 argline = self.fin.readline()[:-1]
29 arg, l = argline.split()
30 arg, l = argline.split()
30 val = self.fin.read(int(l))
31 val = self.fin.read(int(l))
31 return arg, val
32 return arg, val
32
33
33 def respond(self, v):
34 def respond(self, v):
34 self.fout.write("%d\n" % len(v))
35 self.fout.write("%d\n" % len(v))
35 self.fout.write(v)
36 self.fout.write(v)
36 self.fout.flush()
37 self.fout.flush()
37
38
38 def serve_forever(self):
39 def serve_forever(self):
39 while self.serve_one(): pass
40 while self.serve_one(): pass
40 sys.exit(0)
41 sys.exit(0)
41
42
42 def serve_one(self):
43 def serve_one(self):
43 cmd = self.fin.readline()[:-1]
44 cmd = self.fin.readline()[:-1]
44 if cmd:
45 if cmd:
45 impl = getattr(self, 'do_' + cmd, None)
46 impl = getattr(self, 'do_' + cmd, None)
46 if impl: impl()
47 if impl: impl()
47 else: self.respond("")
48 else: self.respond("")
48 return cmd != ''
49 return cmd != ''
49
50
50 def do_lookup(self):
51 def do_lookup(self):
51 arg, key = self.getarg()
52 arg, key = self.getarg()
52 assert arg == 'key'
53 assert arg == 'key'
53 try:
54 try:
54 r = hex(self.repo.lookup(key))
55 r = hex(self.repo.lookup(key))
55 success = 1
56 success = 1
56 except Exception,inst:
57 except Exception,inst:
57 r = str(inst)
58 r = str(inst)
58 success = 0
59 success = 0
59 self.respond("%s %s\n" % (success, r))
60 self.respond("%s %s\n" % (success, r))
60
61
61 def do_heads(self):
62 def do_heads(self):
62 h = self.repo.heads()
63 h = self.repo.heads()
63 self.respond(" ".join(map(hex, h)) + "\n")
64 self.respond(" ".join(map(hex, h)) + "\n")
64
65
65 def do_hello(self):
66 def do_hello(self):
66 '''the hello command returns a set of lines describing various
67 '''the hello command returns a set of lines describing various
67 interesting things about the server, in an RFC822-like format.
68 interesting things about the server, in an RFC822-like format.
68 Currently the only one defined is "capabilities", which
69 Currently the only one defined is "capabilities", which
69 consists of a line in the form:
70 consists of a line in the form:
70
71
71 capabilities: space separated list of tokens
72 capabilities: space separated list of tokens
72 '''
73 '''
73
74
74 caps = ['unbundle', 'lookup', 'changegroupsubset']
75 caps = ['unbundle', 'lookup', 'changegroupsubset']
75 if self.ui.configbool('server', 'uncompressed'):
76 if self.ui.configbool('server', 'uncompressed'):
76 caps.append('stream=%d' % self.repo.changelog.version)
77 caps.append('stream=%d' % self.repo.changelog.version)
77 self.respond("capabilities: %s\n" % (' '.join(caps),))
78 self.respond("capabilities: %s\n" % (' '.join(caps),))
78
79
79 def do_lock(self):
80 def do_lock(self):
80 '''DEPRECATED - allowing remote client to lock repo is not safe'''
81 '''DEPRECATED - allowing remote client to lock repo is not safe'''
81
82
82 self.lock = self.repo.lock()
83 self.lock = self.repo.lock()
83 self.respond("")
84 self.respond("")
84
85
85 def do_unlock(self):
86 def do_unlock(self):
86 '''DEPRECATED'''
87 '''DEPRECATED'''
87
88
88 if self.lock:
89 if self.lock:
89 self.lock.release()
90 self.lock.release()
90 self.lock = None
91 self.lock = None
91 self.respond("")
92 self.respond("")
92
93
93 def do_branches(self):
94 def do_branches(self):
94 arg, nodes = self.getarg()
95 arg, nodes = self.getarg()
95 nodes = map(bin, nodes.split(" "))
96 nodes = map(bin, nodes.split(" "))
96 r = []
97 r = []
97 for b in self.repo.branches(nodes):
98 for b in self.repo.branches(nodes):
98 r.append(" ".join(map(hex, b)) + "\n")
99 r.append(" ".join(map(hex, b)) + "\n")
99 self.respond("".join(r))
100 self.respond("".join(r))
100
101
101 def do_between(self):
102 def do_between(self):
102 arg, pairs = self.getarg()
103 arg, pairs = self.getarg()
103 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
104 pairs = [map(bin, p.split("-")) for p in pairs.split(" ")]
104 r = []
105 r = []
105 for b in self.repo.between(pairs):
106 for b in self.repo.between(pairs):
106 r.append(" ".join(map(hex, b)) + "\n")
107 r.append(" ".join(map(hex, b)) + "\n")
107 self.respond("".join(r))
108 self.respond("".join(r))
108
109
109 def do_changegroup(self):
110 def do_changegroup(self):
110 nodes = []
111 nodes = []
111 arg, roots = self.getarg()
112 arg, roots = self.getarg()
112 nodes = map(bin, roots.split(" "))
113 nodes = map(bin, roots.split(" "))
113
114
114 cg = self.repo.changegroup(nodes, 'serve')
115 cg = self.repo.changegroup(nodes, 'serve')
115 while True:
116 while True:
116 d = cg.read(4096)
117 d = cg.read(4096)
117 if not d:
118 if not d:
118 break
119 break
119 self.fout.write(d)
120 self.fout.write(d)
120
121
121 self.fout.flush()
122 self.fout.flush()
122
123
123 def do_changegroupsubset(self):
124 def do_changegroupsubset(self):
124 bases = []
125 bases = []
125 heads = []
126 heads = []
126 argmap = dict([self.getarg(), self.getarg()])
127 argmap = dict([self.getarg(), self.getarg()])
127 bases = [bin(n) for n in argmap['bases'].split(' ')]
128 bases = [bin(n) for n in argmap['bases'].split(' ')]
128 heads = [bin(n) for n in argmap['heads'].split(' ')]
129 heads = [bin(n) for n in argmap['heads'].split(' ')]
129
130
130 cg = self.repo.changegroupsubset(bases, heads, 'serve')
131 cg = self.repo.changegroupsubset(bases, heads, 'serve')
131 while True:
132 while True:
132 d = cg.read(4096)
133 d = cg.read(4096)
133 if not d:
134 if not d:
134 break
135 break
135 self.fout.write(d)
136 self.fout.write(d)
136
137
137 self.fout.flush()
138 self.fout.flush()
138
139
139 def do_addchangegroup(self):
140 def do_addchangegroup(self):
140 '''DEPRECATED'''
141 '''DEPRECATED'''
141
142
142 if not self.lock:
143 if not self.lock:
143 self.respond("not locked")
144 self.respond("not locked")
144 return
145 return
145
146
146 self.respond("")
147 self.respond("")
147 r = self.repo.addchangegroup(self.fin, 'serve', self.client_url())
148 r = self.repo.addchangegroup(self.fin, 'serve', self.client_url())
148 self.respond(str(r))
149 self.respond(str(r))
149
150
150 def client_url(self):
151 def client_url(self):
151 client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
152 client = os.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
152 return 'remote:ssh:' + client
153 return 'remote:ssh:' + client
153
154
154 def do_unbundle(self):
155 def do_unbundle(self):
155 their_heads = self.getarg()[1].split()
156 their_heads = self.getarg()[1].split()
156
157
157 def check_heads():
158 def check_heads():
158 heads = map(hex, self.repo.heads())
159 heads = map(hex, self.repo.heads())
159 return their_heads == [hex('force')] or their_heads == heads
160 return their_heads == [hex('force')] or their_heads == heads
160
161
161 # fail early if possible
162 # fail early if possible
162 if not check_heads():
163 if not check_heads():
163 self.respond(_('unsynced changes'))
164 self.respond(_('unsynced changes'))
164 return
165 return
165
166
166 self.respond('')
167 self.respond('')
167
168
168 # write bundle data to temporary file because it can be big
169 # write bundle data to temporary file because it can be big
169
170
170 try:
171 try:
171 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
172 fd, tempname = tempfile.mkstemp(prefix='hg-unbundle-')
172 fp = os.fdopen(fd, 'wb+')
173 fp = os.fdopen(fd, 'wb+')
173
174
174 count = int(self.fin.readline())
175 count = int(self.fin.readline())
175 while count:
176 while count:
176 fp.write(self.fin.read(count))
177 fp.write(self.fin.read(count))
177 count = int(self.fin.readline())
178 count = int(self.fin.readline())
178
179
179 was_locked = self.lock is not None
180 was_locked = self.lock is not None
180 if not was_locked:
181 if not was_locked:
181 self.lock = self.repo.lock()
182 self.lock = self.repo.lock()
182 try:
183 try:
183 if not check_heads():
184 if not check_heads():
184 # someone else committed/pushed/unbundled while we
185 # someone else committed/pushed/unbundled while we
185 # were transferring data
186 # were transferring data
186 self.respond(_('unsynced changes'))
187 self.respond(_('unsynced changes'))
187 return
188 return
188 self.respond('')
189 self.respond('')
189
190
190 # push can proceed
191 # push can proceed
191
192
192 fp.seek(0)
193 fp.seek(0)
193 r = self.repo.addchangegroup(fp, 'serve', self.client_url())
194 r = self.repo.addchangegroup(fp, 'serve', self.client_url())
194 self.respond(str(r))
195 self.respond(str(r))
195 finally:
196 finally:
196 if not was_locked:
197 if not was_locked:
197 self.lock.release()
198 self.lock.release()
198 self.lock = None
199 self.lock = None
199 finally:
200 finally:
200 fp.close()
201 fp.close()
201 os.unlink(tempname)
202 os.unlink(tempname)
202
203
203 def do_stream_out(self):
204 def do_stream_out(self):
204 streamclone.stream_out(self.repo, self.fout)
205 streamclone.stream_out(self.repo, self.fout)
General Comments 0
You need to be logged in to leave comments. Login now