##// END OF EJS Templates
templates: add support for summary webcommand in json style...
Laura Médioni -
r29382:e4b777fe default
parent child Browse files
Show More
@@ -1,1298 +1,1298 b''
1 #
1 #
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import cgi
10 import cgi
11 import copy
11 import copy
12 import mimetypes
12 import mimetypes
13 import os
13 import os
14 import re
14 import re
15
15
16 from ..i18n import _
16 from ..i18n import _
17 from ..node import hex, short
17 from ..node import hex, short
18
18
19 from .common import (
19 from .common import (
20 ErrorResponse,
20 ErrorResponse,
21 HTTP_FORBIDDEN,
21 HTTP_FORBIDDEN,
22 HTTP_NOT_FOUND,
22 HTTP_NOT_FOUND,
23 HTTP_OK,
23 HTTP_OK,
24 get_contact,
24 get_contact,
25 paritygen,
25 paritygen,
26 staticfile,
26 staticfile,
27 )
27 )
28
28
29 from .. import (
29 from .. import (
30 archival,
30 archival,
31 encoding,
31 encoding,
32 error,
32 error,
33 graphmod,
33 graphmod,
34 patch,
34 patch,
35 revset,
35 revset,
36 scmutil,
36 scmutil,
37 templatefilters,
37 templatefilters,
38 templater,
38 templater,
39 util,
39 util,
40 )
40 )
41
41
42 from . import (
42 from . import (
43 webutil,
43 webutil,
44 )
44 )
45
45
46 __all__ = []
46 __all__ = []
47 commands = {}
47 commands = {}
48
48
49 class webcommand(object):
49 class webcommand(object):
50 """Decorator used to register a web command handler.
50 """Decorator used to register a web command handler.
51
51
52 The decorator takes as its positional arguments the name/path the
52 The decorator takes as its positional arguments the name/path the
53 command should be accessible under.
53 command should be accessible under.
54
54
55 Usage:
55 Usage:
56
56
57 @webcommand('mycommand')
57 @webcommand('mycommand')
58 def mycommand(web, req, tmpl):
58 def mycommand(web, req, tmpl):
59 pass
59 pass
60 """
60 """
61
61
62 def __init__(self, name):
62 def __init__(self, name):
63 self.name = name
63 self.name = name
64
64
65 def __call__(self, func):
65 def __call__(self, func):
66 __all__.append(self.name)
66 __all__.append(self.name)
67 commands[self.name] = func
67 commands[self.name] = func
68 return func
68 return func
69
69
70 @webcommand('log')
70 @webcommand('log')
71 def log(web, req, tmpl):
71 def log(web, req, tmpl):
72 """
72 """
73 /log[/{revision}[/{path}]]
73 /log[/{revision}[/{path}]]
74 --------------------------
74 --------------------------
75
75
76 Show repository or file history.
76 Show repository or file history.
77
77
78 For URLs of the form ``/log/{revision}``, a list of changesets starting at
78 For URLs of the form ``/log/{revision}``, a list of changesets starting at
79 the specified changeset identifier is shown. If ``{revision}`` is not
79 the specified changeset identifier is shown. If ``{revision}`` is not
80 defined, the default is ``tip``. This form is equivalent to the
80 defined, the default is ``tip``. This form is equivalent to the
81 ``changelog`` handler.
81 ``changelog`` handler.
82
82
83 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
83 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
84 file will be shown. This form is equivalent to the ``filelog`` handler.
84 file will be shown. This form is equivalent to the ``filelog`` handler.
85 """
85 """
86
86
87 if 'file' in req.form and req.form['file'][0]:
87 if 'file' in req.form and req.form['file'][0]:
88 return filelog(web, req, tmpl)
88 return filelog(web, req, tmpl)
89 else:
89 else:
90 return changelog(web, req, tmpl)
90 return changelog(web, req, tmpl)
91
91
92 @webcommand('rawfile')
92 @webcommand('rawfile')
93 def rawfile(web, req, tmpl):
93 def rawfile(web, req, tmpl):
94 guessmime = web.configbool('web', 'guessmime', False)
94 guessmime = web.configbool('web', 'guessmime', False)
95
95
96 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
96 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
97 if not path:
97 if not path:
98 content = manifest(web, req, tmpl)
98 content = manifest(web, req, tmpl)
99 req.respond(HTTP_OK, web.ctype)
99 req.respond(HTTP_OK, web.ctype)
100 return content
100 return content
101
101
102 try:
102 try:
103 fctx = webutil.filectx(web.repo, req)
103 fctx = webutil.filectx(web.repo, req)
104 except error.LookupError as inst:
104 except error.LookupError as inst:
105 try:
105 try:
106 content = manifest(web, req, tmpl)
106 content = manifest(web, req, tmpl)
107 req.respond(HTTP_OK, web.ctype)
107 req.respond(HTTP_OK, web.ctype)
108 return content
108 return content
109 except ErrorResponse:
109 except ErrorResponse:
110 raise inst
110 raise inst
111
111
112 path = fctx.path()
112 path = fctx.path()
113 text = fctx.data()
113 text = fctx.data()
114 mt = 'application/binary'
114 mt = 'application/binary'
115 if guessmime:
115 if guessmime:
116 mt = mimetypes.guess_type(path)[0]
116 mt = mimetypes.guess_type(path)[0]
117 if mt is None:
117 if mt is None:
118 if util.binary(text):
118 if util.binary(text):
119 mt = 'application/binary'
119 mt = 'application/binary'
120 else:
120 else:
121 mt = 'text/plain'
121 mt = 'text/plain'
122 if mt.startswith('text/'):
122 if mt.startswith('text/'):
123 mt += '; charset="%s"' % encoding.encoding
123 mt += '; charset="%s"' % encoding.encoding
124
124
125 req.respond(HTTP_OK, mt, path, body=text)
125 req.respond(HTTP_OK, mt, path, body=text)
126 return []
126 return []
127
127
128 def _filerevision(web, req, tmpl, fctx):
128 def _filerevision(web, req, tmpl, fctx):
129 f = fctx.path()
129 f = fctx.path()
130 text = fctx.data()
130 text = fctx.data()
131 parity = paritygen(web.stripecount)
131 parity = paritygen(web.stripecount)
132
132
133 if util.binary(text):
133 if util.binary(text):
134 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
134 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
135 text = '(binary:%s)' % mt
135 text = '(binary:%s)' % mt
136
136
137 def lines():
137 def lines():
138 for lineno, t in enumerate(text.splitlines(True)):
138 for lineno, t in enumerate(text.splitlines(True)):
139 yield {"line": t,
139 yield {"line": t,
140 "lineid": "l%d" % (lineno + 1),
140 "lineid": "l%d" % (lineno + 1),
141 "linenumber": "% 6d" % (lineno + 1),
141 "linenumber": "% 6d" % (lineno + 1),
142 "parity": next(parity)}
142 "parity": next(parity)}
143
143
144 return tmpl("filerevision",
144 return tmpl("filerevision",
145 file=f,
145 file=f,
146 path=webutil.up(f),
146 path=webutil.up(f),
147 text=lines(),
147 text=lines(),
148 symrev=webutil.symrevorshortnode(req, fctx),
148 symrev=webutil.symrevorshortnode(req, fctx),
149 rename=webutil.renamelink(fctx),
149 rename=webutil.renamelink(fctx),
150 permissions=fctx.manifest().flags(f),
150 permissions=fctx.manifest().flags(f),
151 **webutil.commonentry(web.repo, fctx))
151 **webutil.commonentry(web.repo, fctx))
152
152
153 @webcommand('file')
153 @webcommand('file')
154 def file(web, req, tmpl):
154 def file(web, req, tmpl):
155 """
155 """
156 /file/{revision}[/{path}]
156 /file/{revision}[/{path}]
157 -------------------------
157 -------------------------
158
158
159 Show information about a directory or file in the repository.
159 Show information about a directory or file in the repository.
160
160
161 Info about the ``path`` given as a URL parameter will be rendered.
161 Info about the ``path`` given as a URL parameter will be rendered.
162
162
163 If ``path`` is a directory, information about the entries in that
163 If ``path`` is a directory, information about the entries in that
164 directory will be rendered. This form is equivalent to the ``manifest``
164 directory will be rendered. This form is equivalent to the ``manifest``
165 handler.
165 handler.
166
166
167 If ``path`` is a file, information about that file will be shown via
167 If ``path`` is a file, information about that file will be shown via
168 the ``filerevision`` template.
168 the ``filerevision`` template.
169
169
170 If ``path`` is not defined, information about the root directory will
170 If ``path`` is not defined, information about the root directory will
171 be rendered.
171 be rendered.
172 """
172 """
173 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
173 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
174 if not path:
174 if not path:
175 return manifest(web, req, tmpl)
175 return manifest(web, req, tmpl)
176 try:
176 try:
177 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
177 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
178 except error.LookupError as inst:
178 except error.LookupError as inst:
179 try:
179 try:
180 return manifest(web, req, tmpl)
180 return manifest(web, req, tmpl)
181 except ErrorResponse:
181 except ErrorResponse:
182 raise inst
182 raise inst
183
183
184 def _search(web, req, tmpl):
184 def _search(web, req, tmpl):
185 MODE_REVISION = 'rev'
185 MODE_REVISION = 'rev'
186 MODE_KEYWORD = 'keyword'
186 MODE_KEYWORD = 'keyword'
187 MODE_REVSET = 'revset'
187 MODE_REVSET = 'revset'
188
188
189 def revsearch(ctx):
189 def revsearch(ctx):
190 yield ctx
190 yield ctx
191
191
192 def keywordsearch(query):
192 def keywordsearch(query):
193 lower = encoding.lower
193 lower = encoding.lower
194 qw = lower(query).split()
194 qw = lower(query).split()
195
195
196 def revgen():
196 def revgen():
197 cl = web.repo.changelog
197 cl = web.repo.changelog
198 for i in xrange(len(web.repo) - 1, 0, -100):
198 for i in xrange(len(web.repo) - 1, 0, -100):
199 l = []
199 l = []
200 for j in cl.revs(max(0, i - 99), i):
200 for j in cl.revs(max(0, i - 99), i):
201 ctx = web.repo[j]
201 ctx = web.repo[j]
202 l.append(ctx)
202 l.append(ctx)
203 l.reverse()
203 l.reverse()
204 for e in l:
204 for e in l:
205 yield e
205 yield e
206
206
207 for ctx in revgen():
207 for ctx in revgen():
208 miss = 0
208 miss = 0
209 for q in qw:
209 for q in qw:
210 if not (q in lower(ctx.user()) or
210 if not (q in lower(ctx.user()) or
211 q in lower(ctx.description()) or
211 q in lower(ctx.description()) or
212 q in lower(" ".join(ctx.files()))):
212 q in lower(" ".join(ctx.files()))):
213 miss = 1
213 miss = 1
214 break
214 break
215 if miss:
215 if miss:
216 continue
216 continue
217
217
218 yield ctx
218 yield ctx
219
219
220 def revsetsearch(revs):
220 def revsetsearch(revs):
221 for r in revs:
221 for r in revs:
222 yield web.repo[r]
222 yield web.repo[r]
223
223
224 searchfuncs = {
224 searchfuncs = {
225 MODE_REVISION: (revsearch, 'exact revision search'),
225 MODE_REVISION: (revsearch, 'exact revision search'),
226 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
226 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
227 MODE_REVSET: (revsetsearch, 'revset expression search'),
227 MODE_REVSET: (revsetsearch, 'revset expression search'),
228 }
228 }
229
229
230 def getsearchmode(query):
230 def getsearchmode(query):
231 try:
231 try:
232 ctx = web.repo[query]
232 ctx = web.repo[query]
233 except (error.RepoError, error.LookupError):
233 except (error.RepoError, error.LookupError):
234 # query is not an exact revision pointer, need to
234 # query is not an exact revision pointer, need to
235 # decide if it's a revset expression or keywords
235 # decide if it's a revset expression or keywords
236 pass
236 pass
237 else:
237 else:
238 return MODE_REVISION, ctx
238 return MODE_REVISION, ctx
239
239
240 revdef = 'reverse(%s)' % query
240 revdef = 'reverse(%s)' % query
241 try:
241 try:
242 tree = revset.parse(revdef)
242 tree = revset.parse(revdef)
243 except error.ParseError:
243 except error.ParseError:
244 # can't parse to a revset tree
244 # can't parse to a revset tree
245 return MODE_KEYWORD, query
245 return MODE_KEYWORD, query
246
246
247 if revset.depth(tree) <= 2:
247 if revset.depth(tree) <= 2:
248 # no revset syntax used
248 # no revset syntax used
249 return MODE_KEYWORD, query
249 return MODE_KEYWORD, query
250
250
251 if any((token, (value or '')[:3]) == ('string', 're:')
251 if any((token, (value or '')[:3]) == ('string', 're:')
252 for token, value, pos in revset.tokenize(revdef)):
252 for token, value, pos in revset.tokenize(revdef)):
253 return MODE_KEYWORD, query
253 return MODE_KEYWORD, query
254
254
255 funcsused = revset.funcsused(tree)
255 funcsused = revset.funcsused(tree)
256 if not funcsused.issubset(revset.safesymbols):
256 if not funcsused.issubset(revset.safesymbols):
257 return MODE_KEYWORD, query
257 return MODE_KEYWORD, query
258
258
259 mfunc = revset.match(web.repo.ui, revdef)
259 mfunc = revset.match(web.repo.ui, revdef)
260 try:
260 try:
261 revs = mfunc(web.repo)
261 revs = mfunc(web.repo)
262 return MODE_REVSET, revs
262 return MODE_REVSET, revs
263 # ParseError: wrongly placed tokens, wrongs arguments, etc
263 # ParseError: wrongly placed tokens, wrongs arguments, etc
264 # RepoLookupError: no such revision, e.g. in 'revision:'
264 # RepoLookupError: no such revision, e.g. in 'revision:'
265 # Abort: bookmark/tag not exists
265 # Abort: bookmark/tag not exists
266 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
266 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
267 except (error.ParseError, error.RepoLookupError, error.Abort,
267 except (error.ParseError, error.RepoLookupError, error.Abort,
268 LookupError):
268 LookupError):
269 return MODE_KEYWORD, query
269 return MODE_KEYWORD, query
270
270
271 def changelist(**map):
271 def changelist(**map):
272 count = 0
272 count = 0
273
273
274 for ctx in searchfunc[0](funcarg):
274 for ctx in searchfunc[0](funcarg):
275 count += 1
275 count += 1
276 n = ctx.node()
276 n = ctx.node()
277 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
277 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
278 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
278 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
279
279
280 yield tmpl('searchentry',
280 yield tmpl('searchentry',
281 parity=next(parity),
281 parity=next(parity),
282 changelogtag=showtags,
282 changelogtag=showtags,
283 files=files,
283 files=files,
284 **webutil.commonentry(web.repo, ctx))
284 **webutil.commonentry(web.repo, ctx))
285
285
286 if count >= revcount:
286 if count >= revcount:
287 break
287 break
288
288
289 query = req.form['rev'][0]
289 query = req.form['rev'][0]
290 revcount = web.maxchanges
290 revcount = web.maxchanges
291 if 'revcount' in req.form:
291 if 'revcount' in req.form:
292 try:
292 try:
293 revcount = int(req.form.get('revcount', [revcount])[0])
293 revcount = int(req.form.get('revcount', [revcount])[0])
294 revcount = max(revcount, 1)
294 revcount = max(revcount, 1)
295 tmpl.defaults['sessionvars']['revcount'] = revcount
295 tmpl.defaults['sessionvars']['revcount'] = revcount
296 except ValueError:
296 except ValueError:
297 pass
297 pass
298
298
299 lessvars = copy.copy(tmpl.defaults['sessionvars'])
299 lessvars = copy.copy(tmpl.defaults['sessionvars'])
300 lessvars['revcount'] = max(revcount / 2, 1)
300 lessvars['revcount'] = max(revcount / 2, 1)
301 lessvars['rev'] = query
301 lessvars['rev'] = query
302 morevars = copy.copy(tmpl.defaults['sessionvars'])
302 morevars = copy.copy(tmpl.defaults['sessionvars'])
303 morevars['revcount'] = revcount * 2
303 morevars['revcount'] = revcount * 2
304 morevars['rev'] = query
304 morevars['rev'] = query
305
305
306 mode, funcarg = getsearchmode(query)
306 mode, funcarg = getsearchmode(query)
307
307
308 if 'forcekw' in req.form:
308 if 'forcekw' in req.form:
309 showforcekw = ''
309 showforcekw = ''
310 showunforcekw = searchfuncs[mode][1]
310 showunforcekw = searchfuncs[mode][1]
311 mode = MODE_KEYWORD
311 mode = MODE_KEYWORD
312 funcarg = query
312 funcarg = query
313 else:
313 else:
314 if mode != MODE_KEYWORD:
314 if mode != MODE_KEYWORD:
315 showforcekw = searchfuncs[MODE_KEYWORD][1]
315 showforcekw = searchfuncs[MODE_KEYWORD][1]
316 else:
316 else:
317 showforcekw = ''
317 showforcekw = ''
318 showunforcekw = ''
318 showunforcekw = ''
319
319
320 searchfunc = searchfuncs[mode]
320 searchfunc = searchfuncs[mode]
321
321
322 tip = web.repo['tip']
322 tip = web.repo['tip']
323 parity = paritygen(web.stripecount)
323 parity = paritygen(web.stripecount)
324
324
325 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
325 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
326 entries=changelist, archives=web.archivelist("tip"),
326 entries=changelist, archives=web.archivelist("tip"),
327 morevars=morevars, lessvars=lessvars,
327 morevars=morevars, lessvars=lessvars,
328 modedesc=searchfunc[1],
328 modedesc=searchfunc[1],
329 showforcekw=showforcekw, showunforcekw=showunforcekw)
329 showforcekw=showforcekw, showunforcekw=showunforcekw)
330
330
331 @webcommand('changelog')
331 @webcommand('changelog')
332 def changelog(web, req, tmpl, shortlog=False):
332 def changelog(web, req, tmpl, shortlog=False):
333 """
333 """
334 /changelog[/{revision}]
334 /changelog[/{revision}]
335 -----------------------
335 -----------------------
336
336
337 Show information about multiple changesets.
337 Show information about multiple changesets.
338
338
339 If the optional ``revision`` URL argument is absent, information about
339 If the optional ``revision`` URL argument is absent, information about
340 all changesets starting at ``tip`` will be rendered. If the ``revision``
340 all changesets starting at ``tip`` will be rendered. If the ``revision``
341 argument is present, changesets will be shown starting from the specified
341 argument is present, changesets will be shown starting from the specified
342 revision.
342 revision.
343
343
344 If ``revision`` is absent, the ``rev`` query string argument may be
344 If ``revision`` is absent, the ``rev`` query string argument may be
345 defined. This will perform a search for changesets.
345 defined. This will perform a search for changesets.
346
346
347 The argument for ``rev`` can be a single revision, a revision set,
347 The argument for ``rev`` can be a single revision, a revision set,
348 or a literal keyword to search for in changeset data (equivalent to
348 or a literal keyword to search for in changeset data (equivalent to
349 :hg:`log -k`).
349 :hg:`log -k`).
350
350
351 The ``revcount`` query string argument defines the maximum numbers of
351 The ``revcount`` query string argument defines the maximum numbers of
352 changesets to render.
352 changesets to render.
353
353
354 For non-searches, the ``changelog`` template will be rendered.
354 For non-searches, the ``changelog`` template will be rendered.
355 """
355 """
356
356
357 query = ''
357 query = ''
358 if 'node' in req.form:
358 if 'node' in req.form:
359 ctx = webutil.changectx(web.repo, req)
359 ctx = webutil.changectx(web.repo, req)
360 symrev = webutil.symrevorshortnode(req, ctx)
360 symrev = webutil.symrevorshortnode(req, ctx)
361 elif 'rev' in req.form:
361 elif 'rev' in req.form:
362 return _search(web, req, tmpl)
362 return _search(web, req, tmpl)
363 else:
363 else:
364 ctx = web.repo['tip']
364 ctx = web.repo['tip']
365 symrev = 'tip'
365 symrev = 'tip'
366
366
367 def changelist():
367 def changelist():
368 revs = []
368 revs = []
369 if pos != -1:
369 if pos != -1:
370 revs = web.repo.changelog.revs(pos, 0)
370 revs = web.repo.changelog.revs(pos, 0)
371 curcount = 0
371 curcount = 0
372 for rev in revs:
372 for rev in revs:
373 curcount += 1
373 curcount += 1
374 if curcount > revcount + 1:
374 if curcount > revcount + 1:
375 break
375 break
376
376
377 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
377 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
378 entry['parity'] = next(parity)
378 entry['parity'] = next(parity)
379 yield entry
379 yield entry
380
380
381 if shortlog:
381 if shortlog:
382 revcount = web.maxshortchanges
382 revcount = web.maxshortchanges
383 else:
383 else:
384 revcount = web.maxchanges
384 revcount = web.maxchanges
385
385
386 if 'revcount' in req.form:
386 if 'revcount' in req.form:
387 try:
387 try:
388 revcount = int(req.form.get('revcount', [revcount])[0])
388 revcount = int(req.form.get('revcount', [revcount])[0])
389 revcount = max(revcount, 1)
389 revcount = max(revcount, 1)
390 tmpl.defaults['sessionvars']['revcount'] = revcount
390 tmpl.defaults['sessionvars']['revcount'] = revcount
391 except ValueError:
391 except ValueError:
392 pass
392 pass
393
393
394 lessvars = copy.copy(tmpl.defaults['sessionvars'])
394 lessvars = copy.copy(tmpl.defaults['sessionvars'])
395 lessvars['revcount'] = max(revcount / 2, 1)
395 lessvars['revcount'] = max(revcount / 2, 1)
396 morevars = copy.copy(tmpl.defaults['sessionvars'])
396 morevars = copy.copy(tmpl.defaults['sessionvars'])
397 morevars['revcount'] = revcount * 2
397 morevars['revcount'] = revcount * 2
398
398
399 count = len(web.repo)
399 count = len(web.repo)
400 pos = ctx.rev()
400 pos = ctx.rev()
401 parity = paritygen(web.stripecount)
401 parity = paritygen(web.stripecount)
402
402
403 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
403 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
404
404
405 entries = list(changelist())
405 entries = list(changelist())
406 latestentry = entries[:1]
406 latestentry = entries[:1]
407 if len(entries) > revcount:
407 if len(entries) > revcount:
408 nextentry = entries[-1:]
408 nextentry = entries[-1:]
409 entries = entries[:-1]
409 entries = entries[:-1]
410 else:
410 else:
411 nextentry = []
411 nextentry = []
412
412
413 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
413 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
414 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
414 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
415 entries=entries,
415 entries=entries,
416 latestentry=latestentry, nextentry=nextentry,
416 latestentry=latestentry, nextentry=nextentry,
417 archives=web.archivelist("tip"), revcount=revcount,
417 archives=web.archivelist("tip"), revcount=revcount,
418 morevars=morevars, lessvars=lessvars, query=query)
418 morevars=morevars, lessvars=lessvars, query=query)
419
419
420 @webcommand('shortlog')
420 @webcommand('shortlog')
421 def shortlog(web, req, tmpl):
421 def shortlog(web, req, tmpl):
422 """
422 """
423 /shortlog
423 /shortlog
424 ---------
424 ---------
425
425
426 Show basic information about a set of changesets.
426 Show basic information about a set of changesets.
427
427
428 This accepts the same parameters as the ``changelog`` handler. The only
428 This accepts the same parameters as the ``changelog`` handler. The only
429 difference is the ``shortlog`` template will be rendered instead of the
429 difference is the ``shortlog`` template will be rendered instead of the
430 ``changelog`` template.
430 ``changelog`` template.
431 """
431 """
432 return changelog(web, req, tmpl, shortlog=True)
432 return changelog(web, req, tmpl, shortlog=True)
433
433
434 @webcommand('changeset')
434 @webcommand('changeset')
435 def changeset(web, req, tmpl):
435 def changeset(web, req, tmpl):
436 """
436 """
437 /changeset[/{revision}]
437 /changeset[/{revision}]
438 -----------------------
438 -----------------------
439
439
440 Show information about a single changeset.
440 Show information about a single changeset.
441
441
442 A URL path argument is the changeset identifier to show. See ``hg help
442 A URL path argument is the changeset identifier to show. See ``hg help
443 revisions`` for possible values. If not defined, the ``tip`` changeset
443 revisions`` for possible values. If not defined, the ``tip`` changeset
444 will be shown.
444 will be shown.
445
445
446 The ``changeset`` template is rendered. Contents of the ``changesettag``,
446 The ``changeset`` template is rendered. Contents of the ``changesettag``,
447 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
447 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
448 templates related to diffs may all be used to produce the output.
448 templates related to diffs may all be used to produce the output.
449 """
449 """
450 ctx = webutil.changectx(web.repo, req)
450 ctx = webutil.changectx(web.repo, req)
451
451
452 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
452 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
453
453
454 rev = webcommand('rev')(changeset)
454 rev = webcommand('rev')(changeset)
455
455
456 def decodepath(path):
456 def decodepath(path):
457 """Hook for mapping a path in the repository to a path in the
457 """Hook for mapping a path in the repository to a path in the
458 working copy.
458 working copy.
459
459
460 Extensions (e.g., largefiles) can override this to remap files in
460 Extensions (e.g., largefiles) can override this to remap files in
461 the virtual file system presented by the manifest command below."""
461 the virtual file system presented by the manifest command below."""
462 return path
462 return path
463
463
464 @webcommand('manifest')
464 @webcommand('manifest')
465 def manifest(web, req, tmpl):
465 def manifest(web, req, tmpl):
466 """
466 """
467 /manifest[/{revision}[/{path}]]
467 /manifest[/{revision}[/{path}]]
468 -------------------------------
468 -------------------------------
469
469
470 Show information about a directory.
470 Show information about a directory.
471
471
472 If the URL path arguments are omitted, information about the root
472 If the URL path arguments are omitted, information about the root
473 directory for the ``tip`` changeset will be shown.
473 directory for the ``tip`` changeset will be shown.
474
474
475 Because this handler can only show information for directories, it
475 Because this handler can only show information for directories, it
476 is recommended to use the ``file`` handler instead, as it can handle both
476 is recommended to use the ``file`` handler instead, as it can handle both
477 directories and files.
477 directories and files.
478
478
479 The ``manifest`` template will be rendered for this handler.
479 The ``manifest`` template will be rendered for this handler.
480 """
480 """
481 if 'node' in req.form:
481 if 'node' in req.form:
482 ctx = webutil.changectx(web.repo, req)
482 ctx = webutil.changectx(web.repo, req)
483 symrev = webutil.symrevorshortnode(req, ctx)
483 symrev = webutil.symrevorshortnode(req, ctx)
484 else:
484 else:
485 ctx = web.repo['tip']
485 ctx = web.repo['tip']
486 symrev = 'tip'
486 symrev = 'tip'
487 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
487 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
488 mf = ctx.manifest()
488 mf = ctx.manifest()
489 node = ctx.node()
489 node = ctx.node()
490
490
491 files = {}
491 files = {}
492 dirs = {}
492 dirs = {}
493 parity = paritygen(web.stripecount)
493 parity = paritygen(web.stripecount)
494
494
495 if path and path[-1] != "/":
495 if path and path[-1] != "/":
496 path += "/"
496 path += "/"
497 l = len(path)
497 l = len(path)
498 abspath = "/" + path
498 abspath = "/" + path
499
499
500 for full, n in mf.iteritems():
500 for full, n in mf.iteritems():
501 # the virtual path (working copy path) used for the full
501 # the virtual path (working copy path) used for the full
502 # (repository) path
502 # (repository) path
503 f = decodepath(full)
503 f = decodepath(full)
504
504
505 if f[:l] != path:
505 if f[:l] != path:
506 continue
506 continue
507 remain = f[l:]
507 remain = f[l:]
508 elements = remain.split('/')
508 elements = remain.split('/')
509 if len(elements) == 1:
509 if len(elements) == 1:
510 files[remain] = full
510 files[remain] = full
511 else:
511 else:
512 h = dirs # need to retain ref to dirs (root)
512 h = dirs # need to retain ref to dirs (root)
513 for elem in elements[0:-1]:
513 for elem in elements[0:-1]:
514 if elem not in h:
514 if elem not in h:
515 h[elem] = {}
515 h[elem] = {}
516 h = h[elem]
516 h = h[elem]
517 if len(h) > 1:
517 if len(h) > 1:
518 break
518 break
519 h[None] = None # denotes files present
519 h[None] = None # denotes files present
520
520
521 if mf and not files and not dirs:
521 if mf and not files and not dirs:
522 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
522 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
523
523
524 def filelist(**map):
524 def filelist(**map):
525 for f in sorted(files):
525 for f in sorted(files):
526 full = files[f]
526 full = files[f]
527
527
528 fctx = ctx.filectx(full)
528 fctx = ctx.filectx(full)
529 yield {"file": full,
529 yield {"file": full,
530 "parity": next(parity),
530 "parity": next(parity),
531 "basename": f,
531 "basename": f,
532 "date": fctx.date(),
532 "date": fctx.date(),
533 "size": fctx.size(),
533 "size": fctx.size(),
534 "permissions": mf.flags(full)}
534 "permissions": mf.flags(full)}
535
535
536 def dirlist(**map):
536 def dirlist(**map):
537 for d in sorted(dirs):
537 for d in sorted(dirs):
538
538
539 emptydirs = []
539 emptydirs = []
540 h = dirs[d]
540 h = dirs[d]
541 while isinstance(h, dict) and len(h) == 1:
541 while isinstance(h, dict) and len(h) == 1:
542 k, v = h.items()[0]
542 k, v = h.items()[0]
543 if v:
543 if v:
544 emptydirs.append(k)
544 emptydirs.append(k)
545 h = v
545 h = v
546
546
547 path = "%s%s" % (abspath, d)
547 path = "%s%s" % (abspath, d)
548 yield {"parity": next(parity),
548 yield {"parity": next(parity),
549 "path": path,
549 "path": path,
550 "emptydirs": "/".join(emptydirs),
550 "emptydirs": "/".join(emptydirs),
551 "basename": d}
551 "basename": d}
552
552
553 return tmpl("manifest",
553 return tmpl("manifest",
554 symrev=symrev,
554 symrev=symrev,
555 path=abspath,
555 path=abspath,
556 up=webutil.up(abspath),
556 up=webutil.up(abspath),
557 upparity=next(parity),
557 upparity=next(parity),
558 fentries=filelist,
558 fentries=filelist,
559 dentries=dirlist,
559 dentries=dirlist,
560 archives=web.archivelist(hex(node)),
560 archives=web.archivelist(hex(node)),
561 **webutil.commonentry(web.repo, ctx))
561 **webutil.commonentry(web.repo, ctx))
562
562
563 @webcommand('tags')
563 @webcommand('tags')
564 def tags(web, req, tmpl):
564 def tags(web, req, tmpl):
565 """
565 """
566 /tags
566 /tags
567 -----
567 -----
568
568
569 Show information about tags.
569 Show information about tags.
570
570
571 No arguments are accepted.
571 No arguments are accepted.
572
572
573 The ``tags`` template is rendered.
573 The ``tags`` template is rendered.
574 """
574 """
575 i = list(reversed(web.repo.tagslist()))
575 i = list(reversed(web.repo.tagslist()))
576 parity = paritygen(web.stripecount)
576 parity = paritygen(web.stripecount)
577
577
578 def entries(notip, latestonly, **map):
578 def entries(notip, latestonly, **map):
579 t = i
579 t = i
580 if notip:
580 if notip:
581 t = [(k, n) for k, n in i if k != "tip"]
581 t = [(k, n) for k, n in i if k != "tip"]
582 if latestonly:
582 if latestonly:
583 t = t[:1]
583 t = t[:1]
584 for k, n in t:
584 for k, n in t:
585 yield {"parity": next(parity),
585 yield {"parity": next(parity),
586 "tag": k,
586 "tag": k,
587 "date": web.repo[n].date(),
587 "date": web.repo[n].date(),
588 "node": hex(n)}
588 "node": hex(n)}
589
589
590 return tmpl("tags",
590 return tmpl("tags",
591 node=hex(web.repo.changelog.tip()),
591 node=hex(web.repo.changelog.tip()),
592 entries=lambda **x: entries(False, False, **x),
592 entries=lambda **x: entries(False, False, **x),
593 entriesnotip=lambda **x: entries(True, False, **x),
593 entriesnotip=lambda **x: entries(True, False, **x),
594 latestentry=lambda **x: entries(True, True, **x))
594 latestentry=lambda **x: entries(True, True, **x))
595
595
596 @webcommand('bookmarks')
596 @webcommand('bookmarks')
597 def bookmarks(web, req, tmpl):
597 def bookmarks(web, req, tmpl):
598 """
598 """
599 /bookmarks
599 /bookmarks
600 ----------
600 ----------
601
601
602 Show information about bookmarks.
602 Show information about bookmarks.
603
603
604 No arguments are accepted.
604 No arguments are accepted.
605
605
606 The ``bookmarks`` template is rendered.
606 The ``bookmarks`` template is rendered.
607 """
607 """
608 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
608 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
609 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
609 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
610 i = sorted(i, key=sortkey, reverse=True)
610 i = sorted(i, key=sortkey, reverse=True)
611 parity = paritygen(web.stripecount)
611 parity = paritygen(web.stripecount)
612
612
613 def entries(latestonly, **map):
613 def entries(latestonly, **map):
614 t = i
614 t = i
615 if latestonly:
615 if latestonly:
616 t = i[:1]
616 t = i[:1]
617 for k, n in t:
617 for k, n in t:
618 yield {"parity": next(parity),
618 yield {"parity": next(parity),
619 "bookmark": k,
619 "bookmark": k,
620 "date": web.repo[n].date(),
620 "date": web.repo[n].date(),
621 "node": hex(n)}
621 "node": hex(n)}
622
622
623 if i:
623 if i:
624 latestrev = i[0][1]
624 latestrev = i[0][1]
625 else:
625 else:
626 latestrev = -1
626 latestrev = -1
627
627
628 return tmpl("bookmarks",
628 return tmpl("bookmarks",
629 node=hex(web.repo.changelog.tip()),
629 node=hex(web.repo.changelog.tip()),
630 lastchange=[{"date": web.repo[latestrev].date()}],
630 lastchange=[{"date": web.repo[latestrev].date()}],
631 entries=lambda **x: entries(latestonly=False, **x),
631 entries=lambda **x: entries(latestonly=False, **x),
632 latestentry=lambda **x: entries(latestonly=True, **x))
632 latestentry=lambda **x: entries(latestonly=True, **x))
633
633
634 @webcommand('branches')
634 @webcommand('branches')
635 def branches(web, req, tmpl):
635 def branches(web, req, tmpl):
636 """
636 """
637 /branches
637 /branches
638 ---------
638 ---------
639
639
640 Show information about branches.
640 Show information about branches.
641
641
642 All known branches are contained in the output, even closed branches.
642 All known branches are contained in the output, even closed branches.
643
643
644 No arguments are accepted.
644 No arguments are accepted.
645
645
646 The ``branches`` template is rendered.
646 The ``branches`` template is rendered.
647 """
647 """
648 entries = webutil.branchentries(web.repo, web.stripecount)
648 entries = webutil.branchentries(web.repo, web.stripecount)
649 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
649 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
650 return tmpl('branches', node=hex(web.repo.changelog.tip()),
650 return tmpl('branches', node=hex(web.repo.changelog.tip()),
651 entries=entries, latestentry=latestentry)
651 entries=entries, latestentry=latestentry)
652
652
653 @webcommand('summary')
653 @webcommand('summary')
654 def summary(web, req, tmpl):
654 def summary(web, req, tmpl):
655 """
655 """
656 /summary
656 /summary
657 --------
657 --------
658
658
659 Show a summary of repository state.
659 Show a summary of repository state.
660
660
661 Information about the latest changesets, bookmarks, tags, and branches
661 Information about the latest changesets, bookmarks, tags, and branches
662 is captured by this handler.
662 is captured by this handler.
663
663
664 The ``summary`` template is rendered.
664 The ``summary`` template is rendered.
665 """
665 """
666 i = reversed(web.repo.tagslist())
666 i = reversed(web.repo.tagslist())
667
667
668 def tagentries(**map):
668 def tagentries(**map):
669 parity = paritygen(web.stripecount)
669 parity = paritygen(web.stripecount)
670 count = 0
670 count = 0
671 for k, n in i:
671 for k, n in i:
672 if k == "tip": # skip tip
672 if k == "tip": # skip tip
673 continue
673 continue
674
674
675 count += 1
675 count += 1
676 if count > 10: # limit to 10 tags
676 if count > 10: # limit to 10 tags
677 break
677 break
678
678
679 yield tmpl("tagentry",
679 yield tmpl("tagentry",
680 parity=next(parity),
680 parity=next(parity),
681 tag=k,
681 tag=k,
682 node=hex(n),
682 node=hex(n),
683 date=web.repo[n].date())
683 date=web.repo[n].date())
684
684
685 def bookmarks(**map):
685 def bookmarks(**map):
686 parity = paritygen(web.stripecount)
686 parity = paritygen(web.stripecount)
687 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
687 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
688 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
688 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
689 marks = sorted(marks, key=sortkey, reverse=True)
689 marks = sorted(marks, key=sortkey, reverse=True)
690 for k, n in marks[:10]: # limit to 10 bookmarks
690 for k, n in marks[:10]: # limit to 10 bookmarks
691 yield {'parity': next(parity),
691 yield {'parity': next(parity),
692 'bookmark': k,
692 'bookmark': k,
693 'date': web.repo[n].date(),
693 'date': web.repo[n].date(),
694 'node': hex(n)}
694 'node': hex(n)}
695
695
696 def changelist(**map):
696 def changelist(**map):
697 parity = paritygen(web.stripecount, offset=start - end)
697 parity = paritygen(web.stripecount, offset=start - end)
698 l = [] # build a list in forward order for efficiency
698 l = [] # build a list in forward order for efficiency
699 revs = []
699 revs = []
700 if start < end:
700 if start < end:
701 revs = web.repo.changelog.revs(start, end - 1)
701 revs = web.repo.changelog.revs(start, end - 1)
702 for i in revs:
702 for i in revs:
703 ctx = web.repo[i]
703 ctx = web.repo[i]
704
704
705 l.append(tmpl(
705 l.append(tmpl(
706 'shortlogentry',
706 'shortlogentry',
707 parity=next(parity),
707 parity=next(parity),
708 **webutil.commonentry(web.repo, ctx)))
708 **webutil.commonentry(web.repo, ctx)))
709
709
710 l.reverse()
710 for entry in reversed(l):
711 yield l
711 yield entry
712
712
713 tip = web.repo['tip']
713 tip = web.repo['tip']
714 count = len(web.repo)
714 count = len(web.repo)
715 start = max(0, count - web.maxchanges)
715 start = max(0, count - web.maxchanges)
716 end = min(count, start + web.maxchanges)
716 end = min(count, start + web.maxchanges)
717
717
718 return tmpl("summary",
718 return tmpl("summary",
719 desc=web.config("web", "description", "unknown"),
719 desc=web.config("web", "description", "unknown"),
720 owner=get_contact(web.config) or "unknown",
720 owner=get_contact(web.config) or "unknown",
721 lastchange=tip.date(),
721 lastchange=tip.date(),
722 tags=tagentries,
722 tags=tagentries,
723 bookmarks=bookmarks,
723 bookmarks=bookmarks,
724 branches=webutil.branchentries(web.repo, web.stripecount, 10),
724 branches=webutil.branchentries(web.repo, web.stripecount, 10),
725 shortlog=changelist,
725 shortlog=changelist,
726 node=tip.hex(),
726 node=tip.hex(),
727 symrev='tip',
727 symrev='tip',
728 archives=web.archivelist("tip"))
728 archives=web.archivelist("tip"))
729
729
730 @webcommand('filediff')
730 @webcommand('filediff')
731 def filediff(web, req, tmpl):
731 def filediff(web, req, tmpl):
732 """
732 """
733 /diff/{revision}/{path}
733 /diff/{revision}/{path}
734 -----------------------
734 -----------------------
735
735
736 Show how a file changed in a particular commit.
736 Show how a file changed in a particular commit.
737
737
738 The ``filediff`` template is rendered.
738 The ``filediff`` template is rendered.
739
739
740 This handler is registered under both the ``/diff`` and ``/filediff``
740 This handler is registered under both the ``/diff`` and ``/filediff``
741 paths. ``/diff`` is used in modern code.
741 paths. ``/diff`` is used in modern code.
742 """
742 """
743 fctx, ctx = None, None
743 fctx, ctx = None, None
744 try:
744 try:
745 fctx = webutil.filectx(web.repo, req)
745 fctx = webutil.filectx(web.repo, req)
746 except LookupError:
746 except LookupError:
747 ctx = webutil.changectx(web.repo, req)
747 ctx = webutil.changectx(web.repo, req)
748 path = webutil.cleanpath(web.repo, req.form['file'][0])
748 path = webutil.cleanpath(web.repo, req.form['file'][0])
749 if path not in ctx.files():
749 if path not in ctx.files():
750 raise
750 raise
751
751
752 if fctx is not None:
752 if fctx is not None:
753 path = fctx.path()
753 path = fctx.path()
754 ctx = fctx.changectx()
754 ctx = fctx.changectx()
755
755
756 parity = paritygen(web.stripecount)
756 parity = paritygen(web.stripecount)
757 style = web.config('web', 'style', 'paper')
757 style = web.config('web', 'style', 'paper')
758 if 'style' in req.form:
758 if 'style' in req.form:
759 style = req.form['style'][0]
759 style = req.form['style'][0]
760
760
761 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
761 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
762 if fctx is not None:
762 if fctx is not None:
763 rename = webutil.renamelink(fctx)
763 rename = webutil.renamelink(fctx)
764 ctx = fctx
764 ctx = fctx
765 else:
765 else:
766 rename = []
766 rename = []
767 ctx = ctx
767 ctx = ctx
768 return tmpl("filediff",
768 return tmpl("filediff",
769 file=path,
769 file=path,
770 symrev=webutil.symrevorshortnode(req, ctx),
770 symrev=webutil.symrevorshortnode(req, ctx),
771 rename=rename,
771 rename=rename,
772 diff=diffs,
772 diff=diffs,
773 **webutil.commonentry(web.repo, ctx))
773 **webutil.commonentry(web.repo, ctx))
774
774
775 diff = webcommand('diff')(filediff)
775 diff = webcommand('diff')(filediff)
776
776
777 @webcommand('comparison')
777 @webcommand('comparison')
778 def comparison(web, req, tmpl):
778 def comparison(web, req, tmpl):
779 """
779 """
780 /comparison/{revision}/{path}
780 /comparison/{revision}/{path}
781 -----------------------------
781 -----------------------------
782
782
783 Show a comparison between the old and new versions of a file from changes
783 Show a comparison between the old and new versions of a file from changes
784 made on a particular revision.
784 made on a particular revision.
785
785
786 This is similar to the ``diff`` handler. However, this form features
786 This is similar to the ``diff`` handler. However, this form features
787 a split or side-by-side diff rather than a unified diff.
787 a split or side-by-side diff rather than a unified diff.
788
788
789 The ``context`` query string argument can be used to control the lines of
789 The ``context`` query string argument can be used to control the lines of
790 context in the diff.
790 context in the diff.
791
791
792 The ``filecomparison`` template is rendered.
792 The ``filecomparison`` template is rendered.
793 """
793 """
794 ctx = webutil.changectx(web.repo, req)
794 ctx = webutil.changectx(web.repo, req)
795 if 'file' not in req.form:
795 if 'file' not in req.form:
796 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
796 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
797 path = webutil.cleanpath(web.repo, req.form['file'][0])
797 path = webutil.cleanpath(web.repo, req.form['file'][0])
798
798
799 parsecontext = lambda v: v == 'full' and -1 or int(v)
799 parsecontext = lambda v: v == 'full' and -1 or int(v)
800 if 'context' in req.form:
800 if 'context' in req.form:
801 context = parsecontext(req.form['context'][0])
801 context = parsecontext(req.form['context'][0])
802 else:
802 else:
803 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
803 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
804
804
805 def filelines(f):
805 def filelines(f):
806 if util.binary(f.data()):
806 if util.binary(f.data()):
807 mt = mimetypes.guess_type(f.path())[0]
807 mt = mimetypes.guess_type(f.path())[0]
808 if not mt:
808 if not mt:
809 mt = 'application/octet-stream'
809 mt = 'application/octet-stream'
810 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
810 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
811 return f.data().splitlines()
811 return f.data().splitlines()
812
812
813 fctx = None
813 fctx = None
814 parent = ctx.p1()
814 parent = ctx.p1()
815 leftrev = parent.rev()
815 leftrev = parent.rev()
816 leftnode = parent.node()
816 leftnode = parent.node()
817 rightrev = ctx.rev()
817 rightrev = ctx.rev()
818 rightnode = ctx.node()
818 rightnode = ctx.node()
819 if path in ctx:
819 if path in ctx:
820 fctx = ctx[path]
820 fctx = ctx[path]
821 rightlines = filelines(fctx)
821 rightlines = filelines(fctx)
822 if path not in parent:
822 if path not in parent:
823 leftlines = ()
823 leftlines = ()
824 else:
824 else:
825 pfctx = parent[path]
825 pfctx = parent[path]
826 leftlines = filelines(pfctx)
826 leftlines = filelines(pfctx)
827 else:
827 else:
828 rightlines = ()
828 rightlines = ()
829 pfctx = ctx.parents()[0][path]
829 pfctx = ctx.parents()[0][path]
830 leftlines = filelines(pfctx)
830 leftlines = filelines(pfctx)
831
831
832 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
832 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
833 if fctx is not None:
833 if fctx is not None:
834 rename = webutil.renamelink(fctx)
834 rename = webutil.renamelink(fctx)
835 ctx = fctx
835 ctx = fctx
836 else:
836 else:
837 rename = []
837 rename = []
838 ctx = ctx
838 ctx = ctx
839 return tmpl('filecomparison',
839 return tmpl('filecomparison',
840 file=path,
840 file=path,
841 symrev=webutil.symrevorshortnode(req, ctx),
841 symrev=webutil.symrevorshortnode(req, ctx),
842 rename=rename,
842 rename=rename,
843 leftrev=leftrev,
843 leftrev=leftrev,
844 leftnode=hex(leftnode),
844 leftnode=hex(leftnode),
845 rightrev=rightrev,
845 rightrev=rightrev,
846 rightnode=hex(rightnode),
846 rightnode=hex(rightnode),
847 comparison=comparison,
847 comparison=comparison,
848 **webutil.commonentry(web.repo, ctx))
848 **webutil.commonentry(web.repo, ctx))
849
849
850 @webcommand('annotate')
850 @webcommand('annotate')
851 def annotate(web, req, tmpl):
851 def annotate(web, req, tmpl):
852 """
852 """
853 /annotate/{revision}/{path}
853 /annotate/{revision}/{path}
854 ---------------------------
854 ---------------------------
855
855
856 Show changeset information for each line in a file.
856 Show changeset information for each line in a file.
857
857
858 The ``fileannotate`` template is rendered.
858 The ``fileannotate`` template is rendered.
859 """
859 """
860 fctx = webutil.filectx(web.repo, req)
860 fctx = webutil.filectx(web.repo, req)
861 f = fctx.path()
861 f = fctx.path()
862 parity = paritygen(web.stripecount)
862 parity = paritygen(web.stripecount)
863 diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
863 diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
864 section='annotate', whitespace=True)
864 section='annotate', whitespace=True)
865
865
866 def annotate(**map):
866 def annotate(**map):
867 if util.binary(fctx.data()):
867 if util.binary(fctx.data()):
868 mt = (mimetypes.guess_type(fctx.path())[0]
868 mt = (mimetypes.guess_type(fctx.path())[0]
869 or 'application/octet-stream')
869 or 'application/octet-stream')
870 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
870 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
871 '(binary:%s)' % mt)])
871 '(binary:%s)' % mt)])
872 else:
872 else:
873 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
873 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
874 diffopts=diffopts))
874 diffopts=diffopts))
875 for lineno, ((f, targetline), l) in lines:
875 for lineno, ((f, targetline), l) in lines:
876 yield {"parity": next(parity),
876 yield {"parity": next(parity),
877 "node": f.hex(),
877 "node": f.hex(),
878 "rev": f.rev(),
878 "rev": f.rev(),
879 "author": f.user(),
879 "author": f.user(),
880 "desc": f.description(),
880 "desc": f.description(),
881 "extra": f.extra(),
881 "extra": f.extra(),
882 "file": f.path(),
882 "file": f.path(),
883 "targetline": targetline,
883 "targetline": targetline,
884 "line": l,
884 "line": l,
885 "lineno": lineno + 1,
885 "lineno": lineno + 1,
886 "lineid": "l%d" % (lineno + 1),
886 "lineid": "l%d" % (lineno + 1),
887 "linenumber": "% 6d" % (lineno + 1),
887 "linenumber": "% 6d" % (lineno + 1),
888 "revdate": f.date()}
888 "revdate": f.date()}
889
889
890 return tmpl("fileannotate",
890 return tmpl("fileannotate",
891 file=f,
891 file=f,
892 annotate=annotate,
892 annotate=annotate,
893 path=webutil.up(f),
893 path=webutil.up(f),
894 symrev=webutil.symrevorshortnode(req, fctx),
894 symrev=webutil.symrevorshortnode(req, fctx),
895 rename=webutil.renamelink(fctx),
895 rename=webutil.renamelink(fctx),
896 permissions=fctx.manifest().flags(f),
896 permissions=fctx.manifest().flags(f),
897 **webutil.commonentry(web.repo, fctx))
897 **webutil.commonentry(web.repo, fctx))
898
898
899 @webcommand('filelog')
899 @webcommand('filelog')
900 def filelog(web, req, tmpl):
900 def filelog(web, req, tmpl):
901 """
901 """
902 /filelog/{revision}/{path}
902 /filelog/{revision}/{path}
903 --------------------------
903 --------------------------
904
904
905 Show information about the history of a file in the repository.
905 Show information about the history of a file in the repository.
906
906
907 The ``revcount`` query string argument can be defined to control the
907 The ``revcount`` query string argument can be defined to control the
908 maximum number of entries to show.
908 maximum number of entries to show.
909
909
910 The ``filelog`` template will be rendered.
910 The ``filelog`` template will be rendered.
911 """
911 """
912
912
913 try:
913 try:
914 fctx = webutil.filectx(web.repo, req)
914 fctx = webutil.filectx(web.repo, req)
915 f = fctx.path()
915 f = fctx.path()
916 fl = fctx.filelog()
916 fl = fctx.filelog()
917 except error.LookupError:
917 except error.LookupError:
918 f = webutil.cleanpath(web.repo, req.form['file'][0])
918 f = webutil.cleanpath(web.repo, req.form['file'][0])
919 fl = web.repo.file(f)
919 fl = web.repo.file(f)
920 numrevs = len(fl)
920 numrevs = len(fl)
921 if not numrevs: # file doesn't exist at all
921 if not numrevs: # file doesn't exist at all
922 raise
922 raise
923 rev = webutil.changectx(web.repo, req).rev()
923 rev = webutil.changectx(web.repo, req).rev()
924 first = fl.linkrev(0)
924 first = fl.linkrev(0)
925 if rev < first: # current rev is from before file existed
925 if rev < first: # current rev is from before file existed
926 raise
926 raise
927 frev = numrevs - 1
927 frev = numrevs - 1
928 while fl.linkrev(frev) > rev:
928 while fl.linkrev(frev) > rev:
929 frev -= 1
929 frev -= 1
930 fctx = web.repo.filectx(f, fl.linkrev(frev))
930 fctx = web.repo.filectx(f, fl.linkrev(frev))
931
931
932 revcount = web.maxshortchanges
932 revcount = web.maxshortchanges
933 if 'revcount' in req.form:
933 if 'revcount' in req.form:
934 try:
934 try:
935 revcount = int(req.form.get('revcount', [revcount])[0])
935 revcount = int(req.form.get('revcount', [revcount])[0])
936 revcount = max(revcount, 1)
936 revcount = max(revcount, 1)
937 tmpl.defaults['sessionvars']['revcount'] = revcount
937 tmpl.defaults['sessionvars']['revcount'] = revcount
938 except ValueError:
938 except ValueError:
939 pass
939 pass
940
940
941 lessvars = copy.copy(tmpl.defaults['sessionvars'])
941 lessvars = copy.copy(tmpl.defaults['sessionvars'])
942 lessvars['revcount'] = max(revcount / 2, 1)
942 lessvars['revcount'] = max(revcount / 2, 1)
943 morevars = copy.copy(tmpl.defaults['sessionvars'])
943 morevars = copy.copy(tmpl.defaults['sessionvars'])
944 morevars['revcount'] = revcount * 2
944 morevars['revcount'] = revcount * 2
945
945
946 count = fctx.filerev() + 1
946 count = fctx.filerev() + 1
947 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
947 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
948 end = min(count, start + revcount) # last rev on this page
948 end = min(count, start + revcount) # last rev on this page
949 parity = paritygen(web.stripecount, offset=start - end)
949 parity = paritygen(web.stripecount, offset=start - end)
950
950
951 def entries():
951 def entries():
952 l = []
952 l = []
953
953
954 repo = web.repo
954 repo = web.repo
955 revs = fctx.filelog().revs(start, end - 1)
955 revs = fctx.filelog().revs(start, end - 1)
956 for i in revs:
956 for i in revs:
957 iterfctx = fctx.filectx(i)
957 iterfctx = fctx.filectx(i)
958
958
959 l.append(dict(
959 l.append(dict(
960 parity=next(parity),
960 parity=next(parity),
961 filerev=i,
961 filerev=i,
962 file=f,
962 file=f,
963 rename=webutil.renamelink(iterfctx),
963 rename=webutil.renamelink(iterfctx),
964 **webutil.commonentry(repo, iterfctx)))
964 **webutil.commonentry(repo, iterfctx)))
965 for e in reversed(l):
965 for e in reversed(l):
966 yield e
966 yield e
967
967
968 entries = list(entries())
968 entries = list(entries())
969 latestentry = entries[:1]
969 latestentry = entries[:1]
970
970
971 revnav = webutil.filerevnav(web.repo, fctx.path())
971 revnav = webutil.filerevnav(web.repo, fctx.path())
972 nav = revnav.gen(end - 1, revcount, count)
972 nav = revnav.gen(end - 1, revcount, count)
973 return tmpl("filelog",
973 return tmpl("filelog",
974 file=f,
974 file=f,
975 nav=nav,
975 nav=nav,
976 symrev=webutil.symrevorshortnode(req, fctx),
976 symrev=webutil.symrevorshortnode(req, fctx),
977 entries=entries,
977 entries=entries,
978 latestentry=latestentry,
978 latestentry=latestentry,
979 revcount=revcount,
979 revcount=revcount,
980 morevars=morevars,
980 morevars=morevars,
981 lessvars=lessvars,
981 lessvars=lessvars,
982 **webutil.commonentry(web.repo, fctx))
982 **webutil.commonentry(web.repo, fctx))
983
983
984 @webcommand('archive')
984 @webcommand('archive')
985 def archive(web, req, tmpl):
985 def archive(web, req, tmpl):
986 """
986 """
987 /archive/{revision}.{format}[/{path}]
987 /archive/{revision}.{format}[/{path}]
988 -------------------------------------
988 -------------------------------------
989
989
990 Obtain an archive of repository content.
990 Obtain an archive of repository content.
991
991
992 The content and type of the archive is defined by a URL path parameter.
992 The content and type of the archive is defined by a URL path parameter.
993 ``format`` is the file extension of the archive type to be generated. e.g.
993 ``format`` is the file extension of the archive type to be generated. e.g.
994 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
994 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
995 server configuration.
995 server configuration.
996
996
997 The optional ``path`` URL parameter controls content to include in the
997 The optional ``path`` URL parameter controls content to include in the
998 archive. If omitted, every file in the specified revision is present in the
998 archive. If omitted, every file in the specified revision is present in the
999 archive. If included, only the specified file or contents of the specified
999 archive. If included, only the specified file or contents of the specified
1000 directory will be included in the archive.
1000 directory will be included in the archive.
1001
1001
1002 No template is used for this handler. Raw, binary content is generated.
1002 No template is used for this handler. Raw, binary content is generated.
1003 """
1003 """
1004
1004
1005 type_ = req.form.get('type', [None])[0]
1005 type_ = req.form.get('type', [None])[0]
1006 allowed = web.configlist("web", "allow_archive")
1006 allowed = web.configlist("web", "allow_archive")
1007 key = req.form['node'][0]
1007 key = req.form['node'][0]
1008
1008
1009 if type_ not in web.archives:
1009 if type_ not in web.archives:
1010 msg = 'Unsupported archive type: %s' % type_
1010 msg = 'Unsupported archive type: %s' % type_
1011 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1011 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1012
1012
1013 if not ((type_ in allowed or
1013 if not ((type_ in allowed or
1014 web.configbool("web", "allow" + type_, False))):
1014 web.configbool("web", "allow" + type_, False))):
1015 msg = 'Archive type not allowed: %s' % type_
1015 msg = 'Archive type not allowed: %s' % type_
1016 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1016 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1017
1017
1018 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1018 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1019 cnode = web.repo.lookup(key)
1019 cnode = web.repo.lookup(key)
1020 arch_version = key
1020 arch_version = key
1021 if cnode == key or key == 'tip':
1021 if cnode == key or key == 'tip':
1022 arch_version = short(cnode)
1022 arch_version = short(cnode)
1023 name = "%s-%s" % (reponame, arch_version)
1023 name = "%s-%s" % (reponame, arch_version)
1024
1024
1025 ctx = webutil.changectx(web.repo, req)
1025 ctx = webutil.changectx(web.repo, req)
1026 pats = []
1026 pats = []
1027 matchfn = scmutil.match(ctx, [])
1027 matchfn = scmutil.match(ctx, [])
1028 file = req.form.get('file', None)
1028 file = req.form.get('file', None)
1029 if file:
1029 if file:
1030 pats = ['path:' + file[0]]
1030 pats = ['path:' + file[0]]
1031 matchfn = scmutil.match(ctx, pats, default='path')
1031 matchfn = scmutil.match(ctx, pats, default='path')
1032 if pats:
1032 if pats:
1033 files = [f for f in ctx.manifest().keys() if matchfn(f)]
1033 files = [f for f in ctx.manifest().keys() if matchfn(f)]
1034 if not files:
1034 if not files:
1035 raise ErrorResponse(HTTP_NOT_FOUND,
1035 raise ErrorResponse(HTTP_NOT_FOUND,
1036 'file(s) not found: %s' % file[0])
1036 'file(s) not found: %s' % file[0])
1037
1037
1038 mimetype, artype, extension, encoding = web.archivespecs[type_]
1038 mimetype, artype, extension, encoding = web.archivespecs[type_]
1039 headers = [
1039 headers = [
1040 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1040 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1041 ]
1041 ]
1042 if encoding:
1042 if encoding:
1043 headers.append(('Content-Encoding', encoding))
1043 headers.append(('Content-Encoding', encoding))
1044 req.headers.extend(headers)
1044 req.headers.extend(headers)
1045 req.respond(HTTP_OK, mimetype)
1045 req.respond(HTTP_OK, mimetype)
1046
1046
1047 archival.archive(web.repo, req, cnode, artype, prefix=name,
1047 archival.archive(web.repo, req, cnode, artype, prefix=name,
1048 matchfn=matchfn,
1048 matchfn=matchfn,
1049 subrepos=web.configbool("web", "archivesubrepos"))
1049 subrepos=web.configbool("web", "archivesubrepos"))
1050 return []
1050 return []
1051
1051
1052
1052
1053 @webcommand('static')
1053 @webcommand('static')
1054 def static(web, req, tmpl):
1054 def static(web, req, tmpl):
1055 fname = req.form['file'][0]
1055 fname = req.form['file'][0]
1056 # a repo owner may set web.static in .hg/hgrc to get any file
1056 # a repo owner may set web.static in .hg/hgrc to get any file
1057 # readable by the user running the CGI script
1057 # readable by the user running the CGI script
1058 static = web.config("web", "static", None, untrusted=False)
1058 static = web.config("web", "static", None, untrusted=False)
1059 if not static:
1059 if not static:
1060 tp = web.templatepath or templater.templatepaths()
1060 tp = web.templatepath or templater.templatepaths()
1061 if isinstance(tp, str):
1061 if isinstance(tp, str):
1062 tp = [tp]
1062 tp = [tp]
1063 static = [os.path.join(p, 'static') for p in tp]
1063 static = [os.path.join(p, 'static') for p in tp]
1064 staticfile(static, fname, req)
1064 staticfile(static, fname, req)
1065 return []
1065 return []
1066
1066
1067 @webcommand('graph')
1067 @webcommand('graph')
1068 def graph(web, req, tmpl):
1068 def graph(web, req, tmpl):
1069 """
1069 """
1070 /graph[/{revision}]
1070 /graph[/{revision}]
1071 -------------------
1071 -------------------
1072
1072
1073 Show information about the graphical topology of the repository.
1073 Show information about the graphical topology of the repository.
1074
1074
1075 Information rendered by this handler can be used to create visual
1075 Information rendered by this handler can be used to create visual
1076 representations of repository topology.
1076 representations of repository topology.
1077
1077
1078 The ``revision`` URL parameter controls the starting changeset.
1078 The ``revision`` URL parameter controls the starting changeset.
1079
1079
1080 The ``revcount`` query string argument can define the number of changesets
1080 The ``revcount`` query string argument can define the number of changesets
1081 to show information for.
1081 to show information for.
1082
1082
1083 This handler will render the ``graph`` template.
1083 This handler will render the ``graph`` template.
1084 """
1084 """
1085
1085
1086 if 'node' in req.form:
1086 if 'node' in req.form:
1087 ctx = webutil.changectx(web.repo, req)
1087 ctx = webutil.changectx(web.repo, req)
1088 symrev = webutil.symrevorshortnode(req, ctx)
1088 symrev = webutil.symrevorshortnode(req, ctx)
1089 else:
1089 else:
1090 ctx = web.repo['tip']
1090 ctx = web.repo['tip']
1091 symrev = 'tip'
1091 symrev = 'tip'
1092 rev = ctx.rev()
1092 rev = ctx.rev()
1093
1093
1094 bg_height = 39
1094 bg_height = 39
1095 revcount = web.maxshortchanges
1095 revcount = web.maxshortchanges
1096 if 'revcount' in req.form:
1096 if 'revcount' in req.form:
1097 try:
1097 try:
1098 revcount = int(req.form.get('revcount', [revcount])[0])
1098 revcount = int(req.form.get('revcount', [revcount])[0])
1099 revcount = max(revcount, 1)
1099 revcount = max(revcount, 1)
1100 tmpl.defaults['sessionvars']['revcount'] = revcount
1100 tmpl.defaults['sessionvars']['revcount'] = revcount
1101 except ValueError:
1101 except ValueError:
1102 pass
1102 pass
1103
1103
1104 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1104 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1105 lessvars['revcount'] = max(revcount / 2, 1)
1105 lessvars['revcount'] = max(revcount / 2, 1)
1106 morevars = copy.copy(tmpl.defaults['sessionvars'])
1106 morevars = copy.copy(tmpl.defaults['sessionvars'])
1107 morevars['revcount'] = revcount * 2
1107 morevars['revcount'] = revcount * 2
1108
1108
1109 count = len(web.repo)
1109 count = len(web.repo)
1110 pos = rev
1110 pos = rev
1111
1111
1112 uprev = min(max(0, count - 1), rev + revcount)
1112 uprev = min(max(0, count - 1), rev + revcount)
1113 downrev = max(0, rev - revcount)
1113 downrev = max(0, rev - revcount)
1114 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1114 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1115
1115
1116 tree = []
1116 tree = []
1117 if pos != -1:
1117 if pos != -1:
1118 allrevs = web.repo.changelog.revs(pos, 0)
1118 allrevs = web.repo.changelog.revs(pos, 0)
1119 revs = []
1119 revs = []
1120 for i in allrevs:
1120 for i in allrevs:
1121 revs.append(i)
1121 revs.append(i)
1122 if len(revs) >= revcount:
1122 if len(revs) >= revcount:
1123 break
1123 break
1124
1124
1125 # We have to feed a baseset to dagwalker as it is expecting smartset
1125 # We have to feed a baseset to dagwalker as it is expecting smartset
1126 # object. This does not have a big impact on hgweb performance itself
1126 # object. This does not have a big impact on hgweb performance itself
1127 # since hgweb graphing code is not itself lazy yet.
1127 # since hgweb graphing code is not itself lazy yet.
1128 dag = graphmod.dagwalker(web.repo, revset.baseset(revs))
1128 dag = graphmod.dagwalker(web.repo, revset.baseset(revs))
1129 # As we said one line above... not lazy.
1129 # As we said one line above... not lazy.
1130 tree = list(graphmod.colored(dag, web.repo))
1130 tree = list(graphmod.colored(dag, web.repo))
1131
1131
1132 def getcolumns(tree):
1132 def getcolumns(tree):
1133 cols = 0
1133 cols = 0
1134 for (id, type, ctx, vtx, edges) in tree:
1134 for (id, type, ctx, vtx, edges) in tree:
1135 if type != graphmod.CHANGESET:
1135 if type != graphmod.CHANGESET:
1136 continue
1136 continue
1137 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1137 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1138 max([edge[1] for edge in edges] or [0]))
1138 max([edge[1] for edge in edges] or [0]))
1139 return cols
1139 return cols
1140
1140
1141 def graphdata(usetuples, encodestr):
1141 def graphdata(usetuples, encodestr):
1142 data = []
1142 data = []
1143
1143
1144 row = 0
1144 row = 0
1145 for (id, type, ctx, vtx, edges) in tree:
1145 for (id, type, ctx, vtx, edges) in tree:
1146 if type != graphmod.CHANGESET:
1146 if type != graphmod.CHANGESET:
1147 continue
1147 continue
1148 node = str(ctx)
1148 node = str(ctx)
1149 age = encodestr(templatefilters.age(ctx.date()))
1149 age = encodestr(templatefilters.age(ctx.date()))
1150 desc = templatefilters.firstline(encodestr(ctx.description()))
1150 desc = templatefilters.firstline(encodestr(ctx.description()))
1151 desc = cgi.escape(templatefilters.nonempty(desc))
1151 desc = cgi.escape(templatefilters.nonempty(desc))
1152 user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
1152 user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
1153 branch = cgi.escape(encodestr(ctx.branch()))
1153 branch = cgi.escape(encodestr(ctx.branch()))
1154 try:
1154 try:
1155 branchnode = web.repo.branchtip(branch)
1155 branchnode = web.repo.branchtip(branch)
1156 except error.RepoLookupError:
1156 except error.RepoLookupError:
1157 branchnode = None
1157 branchnode = None
1158 branch = branch, branchnode == ctx.node()
1158 branch = branch, branchnode == ctx.node()
1159
1159
1160 if usetuples:
1160 if usetuples:
1161 data.append((node, vtx, edges, desc, user, age, branch,
1161 data.append((node, vtx, edges, desc, user, age, branch,
1162 [cgi.escape(encodestr(x)) for x in ctx.tags()],
1162 [cgi.escape(encodestr(x)) for x in ctx.tags()],
1163 [cgi.escape(encodestr(x))
1163 [cgi.escape(encodestr(x))
1164 for x in ctx.bookmarks()]))
1164 for x in ctx.bookmarks()]))
1165 else:
1165 else:
1166 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1166 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1167 'color': (edge[2] - 1) % 6 + 1,
1167 'color': (edge[2] - 1) % 6 + 1,
1168 'width': edge[3], 'bcolor': edge[4]}
1168 'width': edge[3], 'bcolor': edge[4]}
1169 for edge in edges]
1169 for edge in edges]
1170
1170
1171 data.append(
1171 data.append(
1172 {'node': node,
1172 {'node': node,
1173 'col': vtx[0],
1173 'col': vtx[0],
1174 'color': (vtx[1] - 1) % 6 + 1,
1174 'color': (vtx[1] - 1) % 6 + 1,
1175 'edges': edgedata,
1175 'edges': edgedata,
1176 'row': row,
1176 'row': row,
1177 'nextrow': row + 1,
1177 'nextrow': row + 1,
1178 'desc': desc,
1178 'desc': desc,
1179 'user': user,
1179 'user': user,
1180 'age': age,
1180 'age': age,
1181 'bookmarks': webutil.nodebookmarksdict(
1181 'bookmarks': webutil.nodebookmarksdict(
1182 web.repo, ctx.node()),
1182 web.repo, ctx.node()),
1183 'branches': webutil.nodebranchdict(web.repo, ctx),
1183 'branches': webutil.nodebranchdict(web.repo, ctx),
1184 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1184 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1185 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1185 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1186
1186
1187 row += 1
1187 row += 1
1188
1188
1189 return data
1189 return data
1190
1190
1191 cols = getcolumns(tree)
1191 cols = getcolumns(tree)
1192 rows = len(tree)
1192 rows = len(tree)
1193 canvasheight = (rows + 1) * bg_height - 27
1193 canvasheight = (rows + 1) * bg_height - 27
1194
1194
1195 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1195 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1196 uprev=uprev,
1196 uprev=uprev,
1197 lessvars=lessvars, morevars=morevars, downrev=downrev,
1197 lessvars=lessvars, morevars=morevars, downrev=downrev,
1198 cols=cols, rows=rows,
1198 cols=cols, rows=rows,
1199 canvaswidth=(cols + 1) * bg_height,
1199 canvaswidth=(cols + 1) * bg_height,
1200 truecanvasheight=rows * bg_height,
1200 truecanvasheight=rows * bg_height,
1201 canvasheight=canvasheight, bg_height=bg_height,
1201 canvasheight=canvasheight, bg_height=bg_height,
1202 # {jsdata} will be passed to |json, so it must be in utf-8
1202 # {jsdata} will be passed to |json, so it must be in utf-8
1203 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
1203 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
1204 nodes=lambda **x: graphdata(False, str),
1204 nodes=lambda **x: graphdata(False, str),
1205 node=ctx.hex(), changenav=changenav)
1205 node=ctx.hex(), changenav=changenav)
1206
1206
1207 def _getdoc(e):
1207 def _getdoc(e):
1208 doc = e[0].__doc__
1208 doc = e[0].__doc__
1209 if doc:
1209 if doc:
1210 doc = _(doc).partition('\n')[0]
1210 doc = _(doc).partition('\n')[0]
1211 else:
1211 else:
1212 doc = _('(no help text available)')
1212 doc = _('(no help text available)')
1213 return doc
1213 return doc
1214
1214
1215 @webcommand('help')
1215 @webcommand('help')
1216 def help(web, req, tmpl):
1216 def help(web, req, tmpl):
1217 """
1217 """
1218 /help[/{topic}]
1218 /help[/{topic}]
1219 ---------------
1219 ---------------
1220
1220
1221 Render help documentation.
1221 Render help documentation.
1222
1222
1223 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1223 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1224 is defined, that help topic will be rendered. If not, an index of
1224 is defined, that help topic will be rendered. If not, an index of
1225 available help topics will be rendered.
1225 available help topics will be rendered.
1226
1226
1227 The ``help`` template will be rendered when requesting help for a topic.
1227 The ``help`` template will be rendered when requesting help for a topic.
1228 ``helptopics`` will be rendered for the index of help topics.
1228 ``helptopics`` will be rendered for the index of help topics.
1229 """
1229 """
1230 from .. import commands, help as helpmod # avoid cycle
1230 from .. import commands, help as helpmod # avoid cycle
1231
1231
1232 topicname = req.form.get('node', [None])[0]
1232 topicname = req.form.get('node', [None])[0]
1233 if not topicname:
1233 if not topicname:
1234 def topics(**map):
1234 def topics(**map):
1235 for entries, summary, _doc in helpmod.helptable:
1235 for entries, summary, _doc in helpmod.helptable:
1236 yield {'topic': entries[0], 'summary': summary}
1236 yield {'topic': entries[0], 'summary': summary}
1237
1237
1238 early, other = [], []
1238 early, other = [], []
1239 primary = lambda s: s.partition('|')[0]
1239 primary = lambda s: s.partition('|')[0]
1240 for c, e in commands.table.iteritems():
1240 for c, e in commands.table.iteritems():
1241 doc = _getdoc(e)
1241 doc = _getdoc(e)
1242 if 'DEPRECATED' in doc or c.startswith('debug'):
1242 if 'DEPRECATED' in doc or c.startswith('debug'):
1243 continue
1243 continue
1244 cmd = primary(c)
1244 cmd = primary(c)
1245 if cmd.startswith('^'):
1245 if cmd.startswith('^'):
1246 early.append((cmd[1:], doc))
1246 early.append((cmd[1:], doc))
1247 else:
1247 else:
1248 other.append((cmd, doc))
1248 other.append((cmd, doc))
1249
1249
1250 early.sort()
1250 early.sort()
1251 other.sort()
1251 other.sort()
1252
1252
1253 def earlycommands(**map):
1253 def earlycommands(**map):
1254 for c, doc in early:
1254 for c, doc in early:
1255 yield {'topic': c, 'summary': doc}
1255 yield {'topic': c, 'summary': doc}
1256
1256
1257 def othercommands(**map):
1257 def othercommands(**map):
1258 for c, doc in other:
1258 for c, doc in other:
1259 yield {'topic': c, 'summary': doc}
1259 yield {'topic': c, 'summary': doc}
1260
1260
1261 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1261 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1262 othercommands=othercommands, title='Index')
1262 othercommands=othercommands, title='Index')
1263
1263
1264 # Render an index of sub-topics.
1264 # Render an index of sub-topics.
1265 if topicname in helpmod.subtopics:
1265 if topicname in helpmod.subtopics:
1266 topics = []
1266 topics = []
1267 for entries, summary, _doc in helpmod.subtopics[topicname]:
1267 for entries, summary, _doc in helpmod.subtopics[topicname]:
1268 topics.append({
1268 topics.append({
1269 'topic': '%s.%s' % (topicname, entries[0]),
1269 'topic': '%s.%s' % (topicname, entries[0]),
1270 'basename': entries[0],
1270 'basename': entries[0],
1271 'summary': summary,
1271 'summary': summary,
1272 })
1272 })
1273
1273
1274 return tmpl('helptopics', topics=topics, title=topicname,
1274 return tmpl('helptopics', topics=topics, title=topicname,
1275 subindex=True)
1275 subindex=True)
1276
1276
1277 u = webutil.wsgiui()
1277 u = webutil.wsgiui()
1278 u.verbose = True
1278 u.verbose = True
1279
1279
1280 # Render a page from a sub-topic.
1280 # Render a page from a sub-topic.
1281 if '.' in topicname:
1281 if '.' in topicname:
1282 # TODO implement support for rendering sections, like
1282 # TODO implement support for rendering sections, like
1283 # `hg help` works.
1283 # `hg help` works.
1284 topic, subtopic = topicname.split('.', 1)
1284 topic, subtopic = topicname.split('.', 1)
1285 if topic not in helpmod.subtopics:
1285 if topic not in helpmod.subtopics:
1286 raise ErrorResponse(HTTP_NOT_FOUND)
1286 raise ErrorResponse(HTTP_NOT_FOUND)
1287 else:
1287 else:
1288 topic = topicname
1288 topic = topicname
1289 subtopic = None
1289 subtopic = None
1290
1290
1291 try:
1291 try:
1292 doc = helpmod.help_(u, topic, subtopic=subtopic)
1292 doc = helpmod.help_(u, topic, subtopic=subtopic)
1293 except error.UnknownCommand:
1293 except error.UnknownCommand:
1294 raise ErrorResponse(HTTP_NOT_FOUND)
1294 raise ErrorResponse(HTTP_NOT_FOUND)
1295 return tmpl('help', topic=topicname, doc=doc)
1295 return tmpl('help', topic=topicname, doc=doc)
1296
1296
1297 # tell hggettext to extract docstrings from these functions:
1297 # tell hggettext to extract docstrings from these functions:
1298 i18nfunctions = commands.values()
1298 i18nfunctions = commands.values()
@@ -1,204 +1,218 b''
1 mimetype = 'application/json'
1 mimetype = 'application/json'
2 filerevision = '\{
2 filerevision = '\{
3 "node": {node|json},
3 "node": {node|json},
4 "path": {file|json},
4 "path": {file|json},
5 "date": {date|json},
5 "date": {date|json},
6 "desc": {desc|utf8|json},
6 "desc": {desc|utf8|json},
7 "branch": {if(branch, branch%changesetbranch, "default"|json)},
7 "branch": {if(branch, branch%changesetbranch, "default"|json)},
8 "bookmarks": [{join(bookmarks%changelistentryname, ", ")}],
8 "bookmarks": [{join(bookmarks%changelistentryname, ", ")}],
9 "tags": [{join(tags%changelistentryname, ", ")}],
9 "tags": [{join(tags%changelistentryname, ", ")}],
10 "user": {author|utf8|json},
10 "user": {author|utf8|json},
11 "parents": [{join(parent%changesetparent, ", ")}],
11 "parents": [{join(parent%changesetparent, ", ")}],
12 "phase": {phase|json},
12 "phase": {phase|json},
13 "lines": [{join(text%lineentry, ", ")}]
13 "lines": [{join(text%lineentry, ", ")}]
14 }'
14 }'
15 lineentry = '\{
15 lineentry = '\{
16 "line": {line|json}
16 "line": {line|json}
17 }'
17 }'
18 search = '"not yet implemented"'
18 search = '"not yet implemented"'
19 # changelog and shortlog are the same web API but with different
19 # changelog and shortlog are the same web API but with different
20 # number of entries.
20 # number of entries.
21 changelog = changelist.tmpl
21 changelog = changelist.tmpl
22 shortlog = changelist.tmpl
22 shortlog = changelist.tmpl
23 changelistentry = '\{
23 changelistentry = '\{
24 "node": {node|json},
24 "node": {node|json},
25 "date": {date|json},
25 "date": {date|json},
26 "desc": {desc|utf8|json},
26 "desc": {desc|utf8|json},
27 "branch": {if(branch, branch%changesetbranch, "default"|json)},
27 "branch": {if(branch, branch%changesetbranch, "default"|json)},
28 "bookmarks": [{join(bookmarks%changelistentryname, ", ")}],
28 "bookmarks": [{join(bookmarks%changelistentryname, ", ")}],
29 "tags": [{join(tags%changelistentryname, ", ")}],
29 "tags": [{join(tags%changelistentryname, ", ")}],
30 "user": {author|utf8|json},
30 "user": {author|utf8|json},
31 "phase": {phase|json},
31 "phase": {phase|json},
32 "parents": [{if(allparents, join(allparents%changesetparent, ", "),
32 "parents": [{if(allparents, join(allparents%changesetparent, ", "),
33 join(parent%changesetparent, ", "))}]
33 join(parent%changesetparent, ", "))}]
34 }'
34 }'
35 changelistentryname = '{name|utf8|json}'
35 changelistentryname = '{name|utf8|json}'
36 changeset = '\{
36 changeset = '\{
37 "node": {node|json},
37 "node": {node|json},
38 "date": {date|json},
38 "date": {date|json},
39 "desc": {desc|utf8|json},
39 "desc": {desc|utf8|json},
40 "branch": {if(branch, branch%changesetbranch, "default"|json)},
40 "branch": {if(branch, branch%changesetbranch, "default"|json)},
41 "bookmarks": [{join(changesetbookmark, ", ")}],
41 "bookmarks": [{join(changesetbookmark, ", ")}],
42 "tags": [{join(changesettag, ", ")}],
42 "tags": [{join(changesettag, ", ")}],
43 "user": {author|utf8|json},
43 "user": {author|utf8|json},
44 "parents": [{join(parent%changesetparent, ", ")}],
44 "parents": [{join(parent%changesetparent, ", ")}],
45 "phase": {phase|json}
45 "phase": {phase|json}
46 }'
46 }'
47 changesetbranch = '{name|utf8|json}'
47 changesetbranch = '{name|utf8|json}'
48 changesetbookmark = '{bookmark|utf8|json}'
48 changesetbookmark = '{bookmark|utf8|json}'
49 changesettag = '{tag|utf8|json}'
49 changesettag = '{tag|utf8|json}'
50 changesetparent = '{node|json}'
50 changesetparent = '{node|json}'
51 manifest = '\{
51 manifest = '\{
52 "node": {node|json},
52 "node": {node|json},
53 "abspath": {path|json},
53 "abspath": {path|json},
54 "directories": [{join(dentries%direntry, ", ")}],
54 "directories": [{join(dentries%direntry, ", ")}],
55 "files": [{join(fentries%fileentry, ", ")}],
55 "files": [{join(fentries%fileentry, ", ")}],
56 "bookmarks": [{join(bookmarks%name, ", ")}],
56 "bookmarks": [{join(bookmarks%name, ", ")}],
57 "tags": [{join(tags%name, ", ")}]
57 "tags": [{join(tags%name, ", ")}]
58 }'
58 }'
59 name = '{name|utf8|json}'
59 name = '{name|utf8|json}'
60 direntry = '\{
60 direntry = '\{
61 "abspath": {path|json},
61 "abspath": {path|json},
62 "basename": {basename|json},
62 "basename": {basename|json},
63 "emptydirs": {emptydirs|json}
63 "emptydirs": {emptydirs|json}
64 }'
64 }'
65 fileentry = '\{
65 fileentry = '\{
66 "abspath": {file|json},
66 "abspath": {file|json},
67 "basename": {basename|json},
67 "basename": {basename|json},
68 "date": {date|json},
68 "date": {date|json},
69 "size": {size|json},
69 "size": {size|json},
70 "flags": {permissions|json}
70 "flags": {permissions|json}
71 }'
71 }'
72 tags = '\{
72 tags = '\{
73 "node": {node|json},
73 "node": {node|json},
74 "tags": [{join(entriesnotip%tagentry, ", ")}]
74 "tags": [{join(entriesnotip%tagentry, ", ")}]
75 }'
75 }'
76 tagentry = '\{
76 tagentry = '\{
77 "tag": {tag|utf8|json},
77 "tag": {tag|utf8|json},
78 "node": {node|json},
78 "node": {node|json},
79 "date": {date|json}
79 "date": {date|json}
80 }'
80 }'
81 bookmarks = '\{
81 bookmarks = '\{
82 "node": {node|json},
82 "node": {node|json},
83 "bookmarks": [{join(entries%bookmarkentry, ", ")}]
83 "bookmarks": [{join(entries%bookmarkentry, ", ")}]
84 }'
84 }'
85 bookmarkentry = '\{
85 bookmarkentry = '\{
86 "bookmark": {bookmark|utf8|json},
86 "bookmark": {bookmark|utf8|json},
87 "node": {node|json},
87 "node": {node|json},
88 "date": {date|json}
88 "date": {date|json}
89 }'
89 }'
90 branches = '\{
90 branches = '\{
91 "branches": [{join(entries%branchentry, ", ")}]
91 "branches": [{join(entries%branchentry, ", ")}]
92 }'
92 }'
93 branchentry = '\{
93 branchentry = '\{
94 "branch": {branch|utf8|json},
94 "branch": {branch|utf8|json},
95 "node": {node|json},
95 "node": {node|json},
96 "date": {date|json},
96 "date": {date|json},
97 "status": {status|json}
97 "status": {status|json}
98 }'
98 }'
99 summary = '"not yet implemented"'
99 shortlogentry = '{changelistentry}'
100 summary = '\{
101 "node": {node|json},
102 "lastchange": {lastchange|json},
103 "bookmarks": [{join(bookmarks%bookmarkentry, ", ")}],
104 "branches": [{join(branches%branchentry, ", ")}],
105 "shortlog": [{join(shortlog%shortlogentry, ", ")}],
106 "tags": [{join(tags%tagentry, ", ")}],
107 "archives": [{join(archives%archiveentry, ", ")}]
108 }'
109 archiveentry = '\{
110 "node": {node|json},
111 "extension": {extension|json},
112 "type": {type|json}
113 }'
100 filediff = '\{
114 filediff = '\{
101 "path": {file|json},
115 "path": {file|json},
102 "node": {node|json},
116 "node": {node|json},
103 "date": {date|json},
117 "date": {date|json},
104 "desc": {desc|utf8|json},
118 "desc": {desc|utf8|json},
105 "author": {author|utf8|json},
119 "author": {author|utf8|json},
106 "parents": [{join(parent%changesetparent, ", ")}],
120 "parents": [{join(parent%changesetparent, ", ")}],
107 "children": [{join(child%changesetparent, ", ")}],
121 "children": [{join(child%changesetparent, ", ")}],
108 "diff": [{join(diff%diffblock, ", ")}]
122 "diff": [{join(diff%diffblock, ", ")}]
109 }'
123 }'
110 diffblock = '\{
124 diffblock = '\{
111 "blockno": {blockno|json},
125 "blockno": {blockno|json},
112 "lines": [{join(lines, ", ")}]
126 "lines": [{join(lines, ", ")}]
113 }'
127 }'
114 difflineplus = '\{
128 difflineplus = '\{
115 "t": "+",
129 "t": "+",
116 "n": {lineno|json},
130 "n": {lineno|json},
117 "l": {line|json}
131 "l": {line|json}
118 }'
132 }'
119 difflineminus = '\{
133 difflineminus = '\{
120 "t": "-",
134 "t": "-",
121 "n": {lineno|json},
135 "n": {lineno|json},
122 "l": {line|json}
136 "l": {line|json}
123 }'
137 }'
124 difflineat = '\{
138 difflineat = '\{
125 "t": "@",
139 "t": "@",
126 "n": {lineno|json},
140 "n": {lineno|json},
127 "l": {line|json}
141 "l": {line|json}
128 }'
142 }'
129 diffline = '\{
143 diffline = '\{
130 "t": "",
144 "t": "",
131 "n": {lineno|json},
145 "n": {lineno|json},
132 "l": {line|json}
146 "l": {line|json}
133 }'
147 }'
134 filecomparison = '\{
148 filecomparison = '\{
135 "path": {file|json},
149 "path": {file|json},
136 "node": {node|json},
150 "node": {node|json},
137 "date": {date|json},
151 "date": {date|json},
138 "desc": {desc|utf8|json},
152 "desc": {desc|utf8|json},
139 "author": {author|utf8|json},
153 "author": {author|utf8|json},
140 "parents": [{join(parent%changesetparent, ", ")}],
154 "parents": [{join(parent%changesetparent, ", ")}],
141 "children": [{join(child%changesetparent, ", ")}],
155 "children": [{join(child%changesetparent, ", ")}],
142 "leftnode": {leftnode|json},
156 "leftnode": {leftnode|json},
143 "rightnode": {rightnode|json},
157 "rightnode": {rightnode|json},
144 "comparison": [{join(comparison, ", ")}]
158 "comparison": [{join(comparison, ", ")}]
145 }'
159 }'
146 comparisonblock = '\{
160 comparisonblock = '\{
147 "lines": [{join(lines, ", ")}]
161 "lines": [{join(lines, ", ")}]
148 }'
162 }'
149 comparisonline = '\{
163 comparisonline = '\{
150 "t": {type|json},
164 "t": {type|json},
151 "ln": {leftlineno|json},
165 "ln": {leftlineno|json},
152 "ll": {leftline|json},
166 "ll": {leftline|json},
153 "rn": {rightlineno|json},
167 "rn": {rightlineno|json},
154 "rl": {rightline|json}
168 "rl": {rightline|json}
155 }'
169 }'
156 fileannotate = '\{
170 fileannotate = '\{
157 "abspath": {file|json},
171 "abspath": {file|json},
158 "node": {node|json},
172 "node": {node|json},
159 "author": {author|utf8|json},
173 "author": {author|utf8|json},
160 "date": {date|json},
174 "date": {date|json},
161 "desc": {desc|utf8|json},
175 "desc": {desc|utf8|json},
162 "parents": [{join(parent%changesetparent, ", ")}],
176 "parents": [{join(parent%changesetparent, ", ")}],
163 "children": [{join(child%changesetparent, ", ")}],
177 "children": [{join(child%changesetparent, ", ")}],
164 "permissions": {permissions|json},
178 "permissions": {permissions|json},
165 "annotate": [{join(annotate%fileannotation, ", ")}]
179 "annotate": [{join(annotate%fileannotation, ", ")}]
166 }'
180 }'
167 fileannotation = '\{
181 fileannotation = '\{
168 "node": {node|json},
182 "node": {node|json},
169 "author": {author|utf8|json},
183 "author": {author|utf8|json},
170 "desc": {desc|utf8|json},
184 "desc": {desc|utf8|json},
171 "abspath": {file|json},
185 "abspath": {file|json},
172 "targetline": {targetline|json},
186 "targetline": {targetline|json},
173 "line": {line|json},
187 "line": {line|json},
174 "lineno": {lineno|json},
188 "lineno": {lineno|json},
175 "revdate": {revdate|json}
189 "revdate": {revdate|json}
176 }'
190 }'
177 filelog = '\{
191 filelog = '\{
178 "entries": [{join(entries%changelistentry, ", ")}]
192 "entries": [{join(entries%changelistentry, ", ")}]
179 }'
193 }'
180 graph = '"not yet implemented"'
194 graph = '"not yet implemented"'
181 helptopics = '\{
195 helptopics = '\{
182 "topics": [{join(topics%helptopicentry, ", ")}],
196 "topics": [{join(topics%helptopicentry, ", ")}],
183 "earlycommands": [{join(earlycommands%helptopicentry, ", ")}],
197 "earlycommands": [{join(earlycommands%helptopicentry, ", ")}],
184 "othercommands": [{join(othercommands%helptopicentry, ", ")}]
198 "othercommands": [{join(othercommands%helptopicentry, ", ")}]
185 }'
199 }'
186 helptopicentry = '\{
200 helptopicentry = '\{
187 "topic": {topic|utf8|json},
201 "topic": {topic|utf8|json},
188 "summary": {summary|utf8|json}
202 "summary": {summary|utf8|json}
189 }'
203 }'
190 help = '\{
204 help = '\{
191 "topic": {topic|utf8|json},
205 "topic": {topic|utf8|json},
192 "rawdoc": {doc|utf8|json}
206 "rawdoc": {doc|utf8|json}
193 }'
207 }'
194 filenodelink = ''
208 filenodelink = ''
195 filenolink = ''
209 filenolink = ''
196 index = '\{
210 index = '\{
197 "entries": [{join(entries%indexentry, ", ")}]
211 "entries": [{join(entries%indexentry, ", ")}]
198 }'
212 }'
199 indexentry = '\{
213 indexentry = '\{
200 "name": {name|utf8|json},
214 "name": {name|utf8|json},
201 "description": {description|utf8|json},
215 "description": {description|utf8|json},
202 "contact": {contact|utf8|json},
216 "contact": {contact|utf8|json},
203 "lastchange": {lastchange|json}
217 "lastchange": {lastchange|json}
204 }'
218 }'
@@ -1,1338 +1,1572 b''
1 #require serve
1 #require serve
2
2
3 $ request() {
3 $ request() {
4 > get-with-headers.py --json localhost:$HGPORT "$1"
4 > get-with-headers.py --json localhost:$HGPORT "$1"
5 > }
5 > }
6
6
7 $ hg init test
7 $ hg init test
8 $ cd test
8 $ cd test
9 $ mkdir da
9 $ mkdir da
10 $ echo foo > da/foo
10 $ echo foo > da/foo
11 $ echo foo > foo
11 $ echo foo > foo
12 $ hg -q ci -A -m initial
12 $ hg -q ci -A -m initial
13 $ echo bar > foo
13 $ echo bar > foo
14 $ hg ci -m 'modify foo'
14 $ hg ci -m 'modify foo'
15 $ echo bar > da/foo
15 $ echo bar > da/foo
16 $ hg ci -m 'modify da/foo'
16 $ hg ci -m 'modify da/foo'
17 $ hg bookmark bookmark1
17 $ hg bookmark bookmark1
18 $ hg up default
18 $ hg up default
19 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
19 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
20 (leaving bookmark bookmark1)
20 (leaving bookmark bookmark1)
21 $ hg mv foo foo-new
21 $ hg mv foo foo-new
22 $ hg commit -m 'move foo'
22 $ hg commit -m 'move foo'
23 $ hg tag -m 'create tag' tag1
23 $ hg tag -m 'create tag' tag1
24 $ hg phase --public -r .
24 $ hg phase --public -r .
25 $ echo baz > da/foo
25 $ echo baz > da/foo
26 $ hg commit -m 'another commit to da/foo'
26 $ hg commit -m 'another commit to da/foo'
27 $ hg tag -m 'create tag2' tag2
27 $ hg tag -m 'create tag2' tag2
28 $ hg bookmark bookmark2
28 $ hg bookmark bookmark2
29 $ hg -q up -r 0
29 $ hg -q up -r 0
30 $ hg -q branch test-branch
30 $ hg -q branch test-branch
31 $ echo branch > foo
31 $ echo branch > foo
32 $ hg commit -m 'create test branch'
32 $ hg commit -m 'create test branch'
33 $ echo branch_commit_2 > foo
33 $ echo branch_commit_2 > foo
34 $ hg commit -m 'another commit in test-branch'
34 $ hg commit -m 'another commit in test-branch'
35 $ hg -q up default
35 $ hg -q up default
36 $ hg merge --tool :local test-branch
36 $ hg merge --tool :local test-branch
37 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
37 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
38 (branch merge, don't forget to commit)
38 (branch merge, don't forget to commit)
39 $ hg commit -m 'merge test-branch into default'
39 $ hg commit -m 'merge test-branch into default'
40
40
41 $ hg log -G
41 $ hg log -G
42 @ changeset: 9:cc725e08502a
42 @ changeset: 9:cc725e08502a
43 |\ tag: tip
43 |\ tag: tip
44 | | parent: 6:ceed296fe500
44 | | parent: 6:ceed296fe500
45 | | parent: 8:ed66c30e87eb
45 | | parent: 8:ed66c30e87eb
46 | | user: test
46 | | user: test
47 | | date: Thu Jan 01 00:00:00 1970 +0000
47 | | date: Thu Jan 01 00:00:00 1970 +0000
48 | | summary: merge test-branch into default
48 | | summary: merge test-branch into default
49 | |
49 | |
50 | o changeset: 8:ed66c30e87eb
50 | o changeset: 8:ed66c30e87eb
51 | | branch: test-branch
51 | | branch: test-branch
52 | | user: test
52 | | user: test
53 | | date: Thu Jan 01 00:00:00 1970 +0000
53 | | date: Thu Jan 01 00:00:00 1970 +0000
54 | | summary: another commit in test-branch
54 | | summary: another commit in test-branch
55 | |
55 | |
56 | o changeset: 7:6ab967a8ab34
56 | o changeset: 7:6ab967a8ab34
57 | | branch: test-branch
57 | | branch: test-branch
58 | | parent: 0:06e557f3edf6
58 | | parent: 0:06e557f3edf6
59 | | user: test
59 | | user: test
60 | | date: Thu Jan 01 00:00:00 1970 +0000
60 | | date: Thu Jan 01 00:00:00 1970 +0000
61 | | summary: create test branch
61 | | summary: create test branch
62 | |
62 | |
63 o | changeset: 6:ceed296fe500
63 o | changeset: 6:ceed296fe500
64 | | bookmark: bookmark2
64 | | bookmark: bookmark2
65 | | user: test
65 | | user: test
66 | | date: Thu Jan 01 00:00:00 1970 +0000
66 | | date: Thu Jan 01 00:00:00 1970 +0000
67 | | summary: create tag2
67 | | summary: create tag2
68 | |
68 | |
69 o | changeset: 5:f2890a05fea4
69 o | changeset: 5:f2890a05fea4
70 | | tag: tag2
70 | | tag: tag2
71 | | user: test
71 | | user: test
72 | | date: Thu Jan 01 00:00:00 1970 +0000
72 | | date: Thu Jan 01 00:00:00 1970 +0000
73 | | summary: another commit to da/foo
73 | | summary: another commit to da/foo
74 | |
74 | |
75 o | changeset: 4:93a8ce14f891
75 o | changeset: 4:93a8ce14f891
76 | | user: test
76 | | user: test
77 | | date: Thu Jan 01 00:00:00 1970 +0000
77 | | date: Thu Jan 01 00:00:00 1970 +0000
78 | | summary: create tag
78 | | summary: create tag
79 | |
79 | |
80 o | changeset: 3:78896eb0e102
80 o | changeset: 3:78896eb0e102
81 | | tag: tag1
81 | | tag: tag1
82 | | user: test
82 | | user: test
83 | | date: Thu Jan 01 00:00:00 1970 +0000
83 | | date: Thu Jan 01 00:00:00 1970 +0000
84 | | summary: move foo
84 | | summary: move foo
85 | |
85 | |
86 o | changeset: 2:8d7c456572ac
86 o | changeset: 2:8d7c456572ac
87 | | bookmark: bookmark1
87 | | bookmark: bookmark1
88 | | user: test
88 | | user: test
89 | | date: Thu Jan 01 00:00:00 1970 +0000
89 | | date: Thu Jan 01 00:00:00 1970 +0000
90 | | summary: modify da/foo
90 | | summary: modify da/foo
91 | |
91 | |
92 o | changeset: 1:f8bbb9024b10
92 o | changeset: 1:f8bbb9024b10
93 |/ user: test
93 |/ user: test
94 | date: Thu Jan 01 00:00:00 1970 +0000
94 | date: Thu Jan 01 00:00:00 1970 +0000
95 | summary: modify foo
95 | summary: modify foo
96 |
96 |
97 o changeset: 0:06e557f3edf6
97 o changeset: 0:06e557f3edf6
98 user: test
98 user: test
99 date: Thu Jan 01 00:00:00 1970 +0000
99 date: Thu Jan 01 00:00:00 1970 +0000
100 summary: initial
100 summary: initial
101
101
102
102
103 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E error.log
103 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E error.log
104 $ cat hg.pid >> $DAEMON_PIDS
104 $ cat hg.pid >> $DAEMON_PIDS
105
105
106 (Try to keep these in roughly the order they are defined in webcommands.py)
106 (Try to keep these in roughly the order they are defined in webcommands.py)
107
107
108 (log is handled by filelog/ and changelog/ - ignore it)
108 (log is handled by filelog/ and changelog/ - ignore it)
109
109
110 (rawfile/ doesn't use templating - nothing to test)
110 (rawfile/ doesn't use templating - nothing to test)
111
111
112 file/{revision}/{path} shows file revision
112 file/{revision}/{path} shows file revision
113
113
114 $ request json-file/78896eb0e102/foo-new
114 $ request json-file/78896eb0e102/foo-new
115 200 Script output follows
115 200 Script output follows
116
116
117 {
117 {
118 "bookmarks": [],
118 "bookmarks": [],
119 "branch": "default",
119 "branch": "default",
120 "date": [
120 "date": [
121 0.0,
121 0.0,
122 0
122 0
123 ],
123 ],
124 "desc": "move foo",
124 "desc": "move foo",
125 "lines": [
125 "lines": [
126 {
126 {
127 "line": "bar\n"
127 "line": "bar\n"
128 }
128 }
129 ],
129 ],
130 "node": "78896eb0e102174ce9278438a95e12543e4367a7",
130 "node": "78896eb0e102174ce9278438a95e12543e4367a7",
131 "parents": [
131 "parents": [
132 "f8bbb9024b10f93cdbb8d940337398291d40dea8"
132 "f8bbb9024b10f93cdbb8d940337398291d40dea8"
133 ],
133 ],
134 "path": "foo-new",
134 "path": "foo-new",
135 "phase": "public",
135 "phase": "public",
136 "tags": [
136 "tags": [
137 "tag1"
137 "tag1"
138 ],
138 ],
139 "user": "test"
139 "user": "test"
140 }
140 }
141
141
142 file/{revision} shows root directory info
142 file/{revision} shows root directory info
143
143
144 $ request json-file/cc725e08502a
144 $ request json-file/cc725e08502a
145 200 Script output follows
145 200 Script output follows
146
146
147 {
147 {
148 "abspath": "/",
148 "abspath": "/",
149 "bookmarks": [],
149 "bookmarks": [],
150 "directories": [
150 "directories": [
151 {
151 {
152 "abspath": "/da",
152 "abspath": "/da",
153 "basename": "da",
153 "basename": "da",
154 "emptydirs": ""
154 "emptydirs": ""
155 }
155 }
156 ],
156 ],
157 "files": [
157 "files": [
158 {
158 {
159 "abspath": ".hgtags",
159 "abspath": ".hgtags",
160 "basename": ".hgtags",
160 "basename": ".hgtags",
161 "date": [
161 "date": [
162 0.0,
162 0.0,
163 0
163 0
164 ],
164 ],
165 "flags": "",
165 "flags": "",
166 "size": 92
166 "size": 92
167 },
167 },
168 {
168 {
169 "abspath": "foo-new",
169 "abspath": "foo-new",
170 "basename": "foo-new",
170 "basename": "foo-new",
171 "date": [
171 "date": [
172 0.0,
172 0.0,
173 0
173 0
174 ],
174 ],
175 "flags": "",
175 "flags": "",
176 "size": 4
176 "size": 4
177 }
177 }
178 ],
178 ],
179 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
179 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
180 "tags": [
180 "tags": [
181 "tip"
181 "tip"
182 ]
182 ]
183 }
183 }
184
184
185 changelog/ shows information about several changesets
185 changelog/ shows information about several changesets
186
186
187 $ request json-changelog
187 $ request json-changelog
188 200 Script output follows
188 200 Script output follows
189
189
190 {
190 {
191 "changeset_count": 10,
191 "changeset_count": 10,
192 "changesets": [
192 "changesets": [
193 {
193 {
194 "bookmarks": [],
194 "bookmarks": [],
195 "branch": "default",
195 "branch": "default",
196 "date": [
196 "date": [
197 0.0,
197 0.0,
198 0
198 0
199 ],
199 ],
200 "desc": "merge test-branch into default",
200 "desc": "merge test-branch into default",
201 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
201 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
202 "parents": [
202 "parents": [
203 "ceed296fe500c3fac9541e31dad860cb49c89e45",
203 "ceed296fe500c3fac9541e31dad860cb49c89e45",
204 "ed66c30e87eb65337c05a4229efaa5f1d5285a90"
204 "ed66c30e87eb65337c05a4229efaa5f1d5285a90"
205 ],
205 ],
206 "phase": "draft",
206 "phase": "draft",
207 "tags": [
207 "tags": [
208 "tip"
208 "tip"
209 ],
209 ],
210 "user": "test"
210 "user": "test"
211 },
211 },
212 {
212 {
213 "bookmarks": [],
213 "bookmarks": [],
214 "branch": "test-branch",
214 "branch": "test-branch",
215 "date": [
215 "date": [
216 0.0,
216 0.0,
217 0
217 0
218 ],
218 ],
219 "desc": "another commit in test-branch",
219 "desc": "another commit in test-branch",
220 "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90",
220 "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90",
221 "parents": [
221 "parents": [
222 "6ab967a8ab3489227a83f80e920faa039a71819f"
222 "6ab967a8ab3489227a83f80e920faa039a71819f"
223 ],
223 ],
224 "phase": "draft",
224 "phase": "draft",
225 "tags": [],
225 "tags": [],
226 "user": "test"
226 "user": "test"
227 },
227 },
228 {
228 {
229 "bookmarks": [],
229 "bookmarks": [],
230 "branch": "test-branch",
230 "branch": "test-branch",
231 "date": [
231 "date": [
232 0.0,
232 0.0,
233 0
233 0
234 ],
234 ],
235 "desc": "create test branch",
235 "desc": "create test branch",
236 "node": "6ab967a8ab3489227a83f80e920faa039a71819f",
236 "node": "6ab967a8ab3489227a83f80e920faa039a71819f",
237 "parents": [
237 "parents": [
238 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
238 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
239 ],
239 ],
240 "phase": "draft",
240 "phase": "draft",
241 "tags": [],
241 "tags": [],
242 "user": "test"
242 "user": "test"
243 },
243 },
244 {
244 {
245 "bookmarks": [
245 "bookmarks": [
246 "bookmark2"
246 "bookmark2"
247 ],
247 ],
248 "branch": "default",
248 "branch": "default",
249 "date": [
249 "date": [
250 0.0,
250 0.0,
251 0
251 0
252 ],
252 ],
253 "desc": "create tag2",
253 "desc": "create tag2",
254 "node": "ceed296fe500c3fac9541e31dad860cb49c89e45",
254 "node": "ceed296fe500c3fac9541e31dad860cb49c89e45",
255 "parents": [
255 "parents": [
256 "f2890a05fea49bfaf9fb27ed5490894eba32da78"
256 "f2890a05fea49bfaf9fb27ed5490894eba32da78"
257 ],
257 ],
258 "phase": "draft",
258 "phase": "draft",
259 "tags": [],
259 "tags": [],
260 "user": "test"
260 "user": "test"
261 },
261 },
262 {
262 {
263 "bookmarks": [],
263 "bookmarks": [],
264 "branch": "default",
264 "branch": "default",
265 "date": [
265 "date": [
266 0.0,
266 0.0,
267 0
267 0
268 ],
268 ],
269 "desc": "another commit to da/foo",
269 "desc": "another commit to da/foo",
270 "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78",
270 "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78",
271 "parents": [
271 "parents": [
272 "93a8ce14f89156426b7fa981af8042da53f03aa0"
272 "93a8ce14f89156426b7fa981af8042da53f03aa0"
273 ],
273 ],
274 "phase": "draft",
274 "phase": "draft",
275 "tags": [
275 "tags": [
276 "tag2"
276 "tag2"
277 ],
277 ],
278 "user": "test"
278 "user": "test"
279 },
279 },
280 {
280 {
281 "bookmarks": [],
281 "bookmarks": [],
282 "branch": "default",
282 "branch": "default",
283 "date": [
283 "date": [
284 0.0,
284 0.0,
285 0
285 0
286 ],
286 ],
287 "desc": "create tag",
287 "desc": "create tag",
288 "node": "93a8ce14f89156426b7fa981af8042da53f03aa0",
288 "node": "93a8ce14f89156426b7fa981af8042da53f03aa0",
289 "parents": [
289 "parents": [
290 "78896eb0e102174ce9278438a95e12543e4367a7"
290 "78896eb0e102174ce9278438a95e12543e4367a7"
291 ],
291 ],
292 "phase": "public",
292 "phase": "public",
293 "tags": [],
293 "tags": [],
294 "user": "test"
294 "user": "test"
295 },
295 },
296 {
296 {
297 "bookmarks": [],
297 "bookmarks": [],
298 "branch": "default",
298 "branch": "default",
299 "date": [
299 "date": [
300 0.0,
300 0.0,
301 0
301 0
302 ],
302 ],
303 "desc": "move foo",
303 "desc": "move foo",
304 "node": "78896eb0e102174ce9278438a95e12543e4367a7",
304 "node": "78896eb0e102174ce9278438a95e12543e4367a7",
305 "parents": [
305 "parents": [
306 "8d7c456572acf3557e8ed8a07286b10c408bcec5"
306 "8d7c456572acf3557e8ed8a07286b10c408bcec5"
307 ],
307 ],
308 "phase": "public",
308 "phase": "public",
309 "tags": [
309 "tags": [
310 "tag1"
310 "tag1"
311 ],
311 ],
312 "user": "test"
312 "user": "test"
313 },
313 },
314 {
314 {
315 "bookmarks": [
315 "bookmarks": [
316 "bookmark1"
316 "bookmark1"
317 ],
317 ],
318 "branch": "default",
318 "branch": "default",
319 "date": [
319 "date": [
320 0.0,
320 0.0,
321 0
321 0
322 ],
322 ],
323 "desc": "modify da/foo",
323 "desc": "modify da/foo",
324 "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5",
324 "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5",
325 "parents": [
325 "parents": [
326 "f8bbb9024b10f93cdbb8d940337398291d40dea8"
326 "f8bbb9024b10f93cdbb8d940337398291d40dea8"
327 ],
327 ],
328 "phase": "public",
328 "phase": "public",
329 "tags": [],
329 "tags": [],
330 "user": "test"
330 "user": "test"
331 },
331 },
332 {
332 {
333 "bookmarks": [],
333 "bookmarks": [],
334 "branch": "default",
334 "branch": "default",
335 "date": [
335 "date": [
336 0.0,
336 0.0,
337 0
337 0
338 ],
338 ],
339 "desc": "modify foo",
339 "desc": "modify foo",
340 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
340 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
341 "parents": [
341 "parents": [
342 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
342 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
343 ],
343 ],
344 "phase": "public",
344 "phase": "public",
345 "tags": [],
345 "tags": [],
346 "user": "test"
346 "user": "test"
347 },
347 },
348 {
348 {
349 "bookmarks": [],
349 "bookmarks": [],
350 "branch": "default",
350 "branch": "default",
351 "date": [
351 "date": [
352 0.0,
352 0.0,
353 0
353 0
354 ],
354 ],
355 "desc": "initial",
355 "desc": "initial",
356 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
356 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
357 "parents": [],
357 "parents": [],
358 "phase": "public",
358 "phase": "public",
359 "tags": [],
359 "tags": [],
360 "user": "test"
360 "user": "test"
361 }
361 }
362 ],
362 ],
363 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7"
363 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7"
364 }
364 }
365
365
366 changelog/{revision} shows information starting at a specific changeset
366 changelog/{revision} shows information starting at a specific changeset
367
367
368 $ request json-changelog/f8bbb9024b10
368 $ request json-changelog/f8bbb9024b10
369 200 Script output follows
369 200 Script output follows
370
370
371 {
371 {
372 "changeset_count": 10,
372 "changeset_count": 10,
373 "changesets": [
373 "changesets": [
374 {
374 {
375 "bookmarks": [],
375 "bookmarks": [],
376 "branch": "default",
376 "branch": "default",
377 "date": [
377 "date": [
378 0.0,
378 0.0,
379 0
379 0
380 ],
380 ],
381 "desc": "modify foo",
381 "desc": "modify foo",
382 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
382 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
383 "parents": [
383 "parents": [
384 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
384 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
385 ],
385 ],
386 "phase": "public",
386 "phase": "public",
387 "tags": [],
387 "tags": [],
388 "user": "test"
388 "user": "test"
389 },
389 },
390 {
390 {
391 "bookmarks": [],
391 "bookmarks": [],
392 "branch": "default",
392 "branch": "default",
393 "date": [
393 "date": [
394 0.0,
394 0.0,
395 0
395 0
396 ],
396 ],
397 "desc": "initial",
397 "desc": "initial",
398 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
398 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
399 "parents": [],
399 "parents": [],
400 "phase": "public",
400 "phase": "public",
401 "tags": [],
401 "tags": [],
402 "user": "test"
402 "user": "test"
403 }
403 }
404 ],
404 ],
405 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8"
405 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8"
406 }
406 }
407
407
408 shortlog/ shows information about a set of changesets
408 shortlog/ shows information about a set of changesets
409
409
410 $ request json-shortlog
410 $ request json-shortlog
411 200 Script output follows
411 200 Script output follows
412
412
413 {
413 {
414 "changeset_count": 10,
414 "changeset_count": 10,
415 "changesets": [
415 "changesets": [
416 {
416 {
417 "bookmarks": [],
417 "bookmarks": [],
418 "branch": "default",
418 "branch": "default",
419 "date": [
419 "date": [
420 0.0,
420 0.0,
421 0
421 0
422 ],
422 ],
423 "desc": "merge test-branch into default",
423 "desc": "merge test-branch into default",
424 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
424 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
425 "parents": [
425 "parents": [
426 "ceed296fe500c3fac9541e31dad860cb49c89e45",
426 "ceed296fe500c3fac9541e31dad860cb49c89e45",
427 "ed66c30e87eb65337c05a4229efaa5f1d5285a90"
427 "ed66c30e87eb65337c05a4229efaa5f1d5285a90"
428 ],
428 ],
429 "phase": "draft",
429 "phase": "draft",
430 "tags": [
430 "tags": [
431 "tip"
431 "tip"
432 ],
432 ],
433 "user": "test"
433 "user": "test"
434 },
434 },
435 {
435 {
436 "bookmarks": [],
436 "bookmarks": [],
437 "branch": "test-branch",
437 "branch": "test-branch",
438 "date": [
438 "date": [
439 0.0,
439 0.0,
440 0
440 0
441 ],
441 ],
442 "desc": "another commit in test-branch",
442 "desc": "another commit in test-branch",
443 "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90",
443 "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90",
444 "parents": [
444 "parents": [
445 "6ab967a8ab3489227a83f80e920faa039a71819f"
445 "6ab967a8ab3489227a83f80e920faa039a71819f"
446 ],
446 ],
447 "phase": "draft",
447 "phase": "draft",
448 "tags": [],
448 "tags": [],
449 "user": "test"
449 "user": "test"
450 },
450 },
451 {
451 {
452 "bookmarks": [],
452 "bookmarks": [],
453 "branch": "test-branch",
453 "branch": "test-branch",
454 "date": [
454 "date": [
455 0.0,
455 0.0,
456 0
456 0
457 ],
457 ],
458 "desc": "create test branch",
458 "desc": "create test branch",
459 "node": "6ab967a8ab3489227a83f80e920faa039a71819f",
459 "node": "6ab967a8ab3489227a83f80e920faa039a71819f",
460 "parents": [
460 "parents": [
461 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
461 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
462 ],
462 ],
463 "phase": "draft",
463 "phase": "draft",
464 "tags": [],
464 "tags": [],
465 "user": "test"
465 "user": "test"
466 },
466 },
467 {
467 {
468 "bookmarks": [
468 "bookmarks": [
469 "bookmark2"
469 "bookmark2"
470 ],
470 ],
471 "branch": "default",
471 "branch": "default",
472 "date": [
472 "date": [
473 0.0,
473 0.0,
474 0
474 0
475 ],
475 ],
476 "desc": "create tag2",
476 "desc": "create tag2",
477 "node": "ceed296fe500c3fac9541e31dad860cb49c89e45",
477 "node": "ceed296fe500c3fac9541e31dad860cb49c89e45",
478 "parents": [
478 "parents": [
479 "f2890a05fea49bfaf9fb27ed5490894eba32da78"
479 "f2890a05fea49bfaf9fb27ed5490894eba32da78"
480 ],
480 ],
481 "phase": "draft",
481 "phase": "draft",
482 "tags": [],
482 "tags": [],
483 "user": "test"
483 "user": "test"
484 },
484 },
485 {
485 {
486 "bookmarks": [],
486 "bookmarks": [],
487 "branch": "default",
487 "branch": "default",
488 "date": [
488 "date": [
489 0.0,
489 0.0,
490 0
490 0
491 ],
491 ],
492 "desc": "another commit to da/foo",
492 "desc": "another commit to da/foo",
493 "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78",
493 "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78",
494 "parents": [
494 "parents": [
495 "93a8ce14f89156426b7fa981af8042da53f03aa0"
495 "93a8ce14f89156426b7fa981af8042da53f03aa0"
496 ],
496 ],
497 "phase": "draft",
497 "phase": "draft",
498 "tags": [
498 "tags": [
499 "tag2"
499 "tag2"
500 ],
500 ],
501 "user": "test"
501 "user": "test"
502 },
502 },
503 {
503 {
504 "bookmarks": [],
504 "bookmarks": [],
505 "branch": "default",
505 "branch": "default",
506 "date": [
506 "date": [
507 0.0,
507 0.0,
508 0
508 0
509 ],
509 ],
510 "desc": "create tag",
510 "desc": "create tag",
511 "node": "93a8ce14f89156426b7fa981af8042da53f03aa0",
511 "node": "93a8ce14f89156426b7fa981af8042da53f03aa0",
512 "parents": [
512 "parents": [
513 "78896eb0e102174ce9278438a95e12543e4367a7"
513 "78896eb0e102174ce9278438a95e12543e4367a7"
514 ],
514 ],
515 "phase": "public",
515 "phase": "public",
516 "tags": [],
516 "tags": [],
517 "user": "test"
517 "user": "test"
518 },
518 },
519 {
519 {
520 "bookmarks": [],
520 "bookmarks": [],
521 "branch": "default",
521 "branch": "default",
522 "date": [
522 "date": [
523 0.0,
523 0.0,
524 0
524 0
525 ],
525 ],
526 "desc": "move foo",
526 "desc": "move foo",
527 "node": "78896eb0e102174ce9278438a95e12543e4367a7",
527 "node": "78896eb0e102174ce9278438a95e12543e4367a7",
528 "parents": [
528 "parents": [
529 "8d7c456572acf3557e8ed8a07286b10c408bcec5"
529 "8d7c456572acf3557e8ed8a07286b10c408bcec5"
530 ],
530 ],
531 "phase": "public",
531 "phase": "public",
532 "tags": [
532 "tags": [
533 "tag1"
533 "tag1"
534 ],
534 ],
535 "user": "test"
535 "user": "test"
536 },
536 },
537 {
537 {
538 "bookmarks": [
538 "bookmarks": [
539 "bookmark1"
539 "bookmark1"
540 ],
540 ],
541 "branch": "default",
541 "branch": "default",
542 "date": [
542 "date": [
543 0.0,
543 0.0,
544 0
544 0
545 ],
545 ],
546 "desc": "modify da/foo",
546 "desc": "modify da/foo",
547 "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5",
547 "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5",
548 "parents": [
548 "parents": [
549 "f8bbb9024b10f93cdbb8d940337398291d40dea8"
549 "f8bbb9024b10f93cdbb8d940337398291d40dea8"
550 ],
550 ],
551 "phase": "public",
551 "phase": "public",
552 "tags": [],
552 "tags": [],
553 "user": "test"
553 "user": "test"
554 },
554 },
555 {
555 {
556 "bookmarks": [],
556 "bookmarks": [],
557 "branch": "default",
557 "branch": "default",
558 "date": [
558 "date": [
559 0.0,
559 0.0,
560 0
560 0
561 ],
561 ],
562 "desc": "modify foo",
562 "desc": "modify foo",
563 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
563 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
564 "parents": [
564 "parents": [
565 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
565 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
566 ],
566 ],
567 "phase": "public",
567 "phase": "public",
568 "tags": [],
568 "tags": [],
569 "user": "test"
569 "user": "test"
570 },
570 },
571 {
571 {
572 "bookmarks": [],
572 "bookmarks": [],
573 "branch": "default",
573 "branch": "default",
574 "date": [
574 "date": [
575 0.0,
575 0.0,
576 0
576 0
577 ],
577 ],
578 "desc": "initial",
578 "desc": "initial",
579 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
579 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
580 "parents": [],
580 "parents": [],
581 "phase": "public",
581 "phase": "public",
582 "tags": [],
582 "tags": [],
583 "user": "test"
583 "user": "test"
584 }
584 }
585 ],
585 ],
586 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7"
586 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7"
587 }
587 }
588
588
589 changeset/ renders the tip changeset
589 changeset/ renders the tip changeset
590
590
591 $ request json-rev
591 $ request json-rev
592 200 Script output follows
592 200 Script output follows
593
593
594 {
594 {
595 "bookmarks": [],
595 "bookmarks": [],
596 "branch": "default",
596 "branch": "default",
597 "date": [
597 "date": [
598 0.0,
598 0.0,
599 0
599 0
600 ],
600 ],
601 "desc": "merge test-branch into default",
601 "desc": "merge test-branch into default",
602 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
602 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
603 "parents": [
603 "parents": [
604 "ceed296fe500c3fac9541e31dad860cb49c89e45",
604 "ceed296fe500c3fac9541e31dad860cb49c89e45",
605 "ed66c30e87eb65337c05a4229efaa5f1d5285a90"
605 "ed66c30e87eb65337c05a4229efaa5f1d5285a90"
606 ],
606 ],
607 "phase": "draft",
607 "phase": "draft",
608 "tags": [
608 "tags": [
609 "tip"
609 "tip"
610 ],
610 ],
611 "user": "test"
611 "user": "test"
612 }
612 }
613
613
614 changeset/{revision} shows tags
614 changeset/{revision} shows tags
615
615
616 $ request json-rev/78896eb0e102
616 $ request json-rev/78896eb0e102
617 200 Script output follows
617 200 Script output follows
618
618
619 {
619 {
620 "bookmarks": [],
620 "bookmarks": [],
621 "branch": "default",
621 "branch": "default",
622 "date": [
622 "date": [
623 0.0,
623 0.0,
624 0
624 0
625 ],
625 ],
626 "desc": "move foo",
626 "desc": "move foo",
627 "node": "78896eb0e102174ce9278438a95e12543e4367a7",
627 "node": "78896eb0e102174ce9278438a95e12543e4367a7",
628 "parents": [
628 "parents": [
629 "8d7c456572acf3557e8ed8a07286b10c408bcec5"
629 "8d7c456572acf3557e8ed8a07286b10c408bcec5"
630 ],
630 ],
631 "phase": "public",
631 "phase": "public",
632 "tags": [
632 "tags": [
633 "tag1"
633 "tag1"
634 ],
634 ],
635 "user": "test"
635 "user": "test"
636 }
636 }
637
637
638 changeset/{revision} shows bookmarks
638 changeset/{revision} shows bookmarks
639
639
640 $ request json-rev/8d7c456572ac
640 $ request json-rev/8d7c456572ac
641 200 Script output follows
641 200 Script output follows
642
642
643 {
643 {
644 "bookmarks": [
644 "bookmarks": [
645 "bookmark1"
645 "bookmark1"
646 ],
646 ],
647 "branch": "default",
647 "branch": "default",
648 "date": [
648 "date": [
649 0.0,
649 0.0,
650 0
650 0
651 ],
651 ],
652 "desc": "modify da/foo",
652 "desc": "modify da/foo",
653 "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5",
653 "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5",
654 "parents": [
654 "parents": [
655 "f8bbb9024b10f93cdbb8d940337398291d40dea8"
655 "f8bbb9024b10f93cdbb8d940337398291d40dea8"
656 ],
656 ],
657 "phase": "public",
657 "phase": "public",
658 "tags": [],
658 "tags": [],
659 "user": "test"
659 "user": "test"
660 }
660 }
661
661
662 changeset/{revision} shows branches
662 changeset/{revision} shows branches
663
663
664 $ request json-rev/6ab967a8ab34
664 $ request json-rev/6ab967a8ab34
665 200 Script output follows
665 200 Script output follows
666
666
667 {
667 {
668 "bookmarks": [],
668 "bookmarks": [],
669 "branch": "test-branch",
669 "branch": "test-branch",
670 "date": [
670 "date": [
671 0.0,
671 0.0,
672 0
672 0
673 ],
673 ],
674 "desc": "create test branch",
674 "desc": "create test branch",
675 "node": "6ab967a8ab3489227a83f80e920faa039a71819f",
675 "node": "6ab967a8ab3489227a83f80e920faa039a71819f",
676 "parents": [
676 "parents": [
677 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
677 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
678 ],
678 ],
679 "phase": "draft",
679 "phase": "draft",
680 "tags": [],
680 "tags": [],
681 "user": "test"
681 "user": "test"
682 }
682 }
683
683
684 manifest/{revision}/{path} shows info about a directory at a revision
684 manifest/{revision}/{path} shows info about a directory at a revision
685
685
686 $ request json-manifest/06e557f3edf6/
686 $ request json-manifest/06e557f3edf6/
687 200 Script output follows
687 200 Script output follows
688
688
689 {
689 {
690 "abspath": "/",
690 "abspath": "/",
691 "bookmarks": [],
691 "bookmarks": [],
692 "directories": [
692 "directories": [
693 {
693 {
694 "abspath": "/da",
694 "abspath": "/da",
695 "basename": "da",
695 "basename": "da",
696 "emptydirs": ""
696 "emptydirs": ""
697 }
697 }
698 ],
698 ],
699 "files": [
699 "files": [
700 {
700 {
701 "abspath": "foo",
701 "abspath": "foo",
702 "basename": "foo",
702 "basename": "foo",
703 "date": [
703 "date": [
704 0.0,
704 0.0,
705 0
705 0
706 ],
706 ],
707 "flags": "",
707 "flags": "",
708 "size": 4
708 "size": 4
709 }
709 }
710 ],
710 ],
711 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
711 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
712 "tags": []
712 "tags": []
713 }
713 }
714
714
715 tags/ shows tags info
715 tags/ shows tags info
716
716
717 $ request json-tags
717 $ request json-tags
718 200 Script output follows
718 200 Script output follows
719
719
720 {
720 {
721 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
721 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
722 "tags": [
722 "tags": [
723 {
723 {
724 "date": [
724 "date": [
725 0.0,
725 0.0,
726 0
726 0
727 ],
727 ],
728 "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78",
728 "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78",
729 "tag": "tag2"
729 "tag": "tag2"
730 },
730 },
731 {
731 {
732 "date": [
732 "date": [
733 0.0,
733 0.0,
734 0
734 0
735 ],
735 ],
736 "node": "78896eb0e102174ce9278438a95e12543e4367a7",
736 "node": "78896eb0e102174ce9278438a95e12543e4367a7",
737 "tag": "tag1"
737 "tag": "tag1"
738 }
738 }
739 ]
739 ]
740 }
740 }
741
741
742 bookmarks/ shows bookmarks info
742 bookmarks/ shows bookmarks info
743
743
744 $ request json-bookmarks
744 $ request json-bookmarks
745 200 Script output follows
745 200 Script output follows
746
746
747 {
747 {
748 "bookmarks": [
748 "bookmarks": [
749 {
749 {
750 "bookmark": "bookmark2",
750 "bookmark": "bookmark2",
751 "date": [
751 "date": [
752 0.0,
752 0.0,
753 0
753 0
754 ],
754 ],
755 "node": "ceed296fe500c3fac9541e31dad860cb49c89e45"
755 "node": "ceed296fe500c3fac9541e31dad860cb49c89e45"
756 },
756 },
757 {
757 {
758 "bookmark": "bookmark1",
758 "bookmark": "bookmark1",
759 "date": [
759 "date": [
760 0.0,
760 0.0,
761 0
761 0
762 ],
762 ],
763 "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5"
763 "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5"
764 }
764 }
765 ],
765 ],
766 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7"
766 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7"
767 }
767 }
768
768
769 branches/ shows branches info
769 branches/ shows branches info
770
770
771 $ request json-branches
771 $ request json-branches
772 200 Script output follows
772 200 Script output follows
773
773
774 {
774 {
775 "branches": [
775 "branches": [
776 {
776 {
777 "branch": "default",
777 "branch": "default",
778 "date": [
778 "date": [
779 0.0,
779 0.0,
780 0
780 0
781 ],
781 ],
782 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
782 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
783 "status": "open"
783 "status": "open"
784 },
784 },
785 {
785 {
786 "branch": "test-branch",
786 "branch": "test-branch",
787 "date": [
787 "date": [
788 0.0,
788 0.0,
789 0
789 0
790 ],
790 ],
791 "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90",
791 "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90",
792 "status": "inactive"
792 "status": "inactive"
793 }
793 }
794 ]
794 ]
795 }
795 }
796
796
797 summary/ shows a summary of repository state
797 summary/ shows a summary of repository state
798
798
799 $ request json-summary
799 $ request json-summary
800 200 Script output follows
800 200 Script output follows
801
801
802 "not yet implemented"
802 {
803 "archives": [],
804 "bookmarks": [
805 {
806 "bookmark": "bookmark2",
807 "date": [
808 0.0,
809 0
810 ],
811 "node": "ceed296fe500c3fac9541e31dad860cb49c89e45"
812 },
813 {
814 "bookmark": "bookmark1",
815 "date": [
816 0.0,
817 0
818 ],
819 "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5"
820 }
821 ],
822 "branches": [
823 {
824 "branch": "default",
825 "date": [
826 0.0,
827 0
828 ],
829 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
830 "status": "open"
831 },
832 {
833 "branch": "test-branch",
834 "date": [
835 0.0,
836 0
837 ],
838 "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90",
839 "status": "inactive"
840 }
841 ],
842 "lastchange": [
843 0.0,
844 0
845 ],
846 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
847 "shortlog": [
848 {
849 "bookmarks": [],
850 "branch": "default",
851 "date": [
852 0.0,
853 0
854 ],
855 "desc": "merge test-branch into default",
856 "node": "cc725e08502a79dd1eda913760fbe06ed7a9abc7",
857 "parents": [
858 "ceed296fe500c3fac9541e31dad860cb49c89e45",
859 "ed66c30e87eb65337c05a4229efaa5f1d5285a90"
860 ],
861 "phase": "draft",
862 "tags": [
863 "tip"
864 ],
865 "user": "test"
866 },
867 {
868 "bookmarks": [],
869 "branch": "test-branch",
870 "date": [
871 0.0,
872 0
873 ],
874 "desc": "another commit in test-branch",
875 "node": "ed66c30e87eb65337c05a4229efaa5f1d5285a90",
876 "parents": [
877 "6ab967a8ab3489227a83f80e920faa039a71819f"
878 ],
879 "phase": "draft",
880 "tags": [],
881 "user": "test"
882 },
883 {
884 "bookmarks": [],
885 "branch": "test-branch",
886 "date": [
887 0.0,
888 0
889 ],
890 "desc": "create test branch",
891 "node": "6ab967a8ab3489227a83f80e920faa039a71819f",
892 "parents": [
893 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
894 ],
895 "phase": "draft",
896 "tags": [],
897 "user": "test"
898 },
899 {
900 "bookmarks": [
901 "bookmark2"
902 ],
903 "branch": "default",
904 "date": [
905 0.0,
906 0
907 ],
908 "desc": "create tag2",
909 "node": "ceed296fe500c3fac9541e31dad860cb49c89e45",
910 "parents": [
911 "f2890a05fea49bfaf9fb27ed5490894eba32da78"
912 ],
913 "phase": "draft",
914 "tags": [],
915 "user": "test"
916 },
917 {
918 "bookmarks": [],
919 "branch": "default",
920 "date": [
921 0.0,
922 0
923 ],
924 "desc": "another commit to da/foo",
925 "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78",
926 "parents": [
927 "93a8ce14f89156426b7fa981af8042da53f03aa0"
928 ],
929 "phase": "draft",
930 "tags": [
931 "tag2"
932 ],
933 "user": "test"
934 },
935 {
936 "bookmarks": [],
937 "branch": "default",
938 "date": [
939 0.0,
940 0
941 ],
942 "desc": "create tag",
943 "node": "93a8ce14f89156426b7fa981af8042da53f03aa0",
944 "parents": [
945 "78896eb0e102174ce9278438a95e12543e4367a7"
946 ],
947 "phase": "public",
948 "tags": [],
949 "user": "test"
950 },
951 {
952 "bookmarks": [],
953 "branch": "default",
954 "date": [
955 0.0,
956 0
957 ],
958 "desc": "move foo",
959 "node": "78896eb0e102174ce9278438a95e12543e4367a7",
960 "parents": [
961 "8d7c456572acf3557e8ed8a07286b10c408bcec5"
962 ],
963 "phase": "public",
964 "tags": [
965 "tag1"
966 ],
967 "user": "test"
968 },
969 {
970 "bookmarks": [
971 "bookmark1"
972 ],
973 "branch": "default",
974 "date": [
975 0.0,
976 0
977 ],
978 "desc": "modify da/foo",
979 "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5",
980 "parents": [
981 "f8bbb9024b10f93cdbb8d940337398291d40dea8"
982 ],
983 "phase": "public",
984 "tags": [],
985 "user": "test"
986 },
987 {
988 "bookmarks": [],
989 "branch": "default",
990 "date": [
991 0.0,
992 0
993 ],
994 "desc": "modify foo",
995 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
996 "parents": [
997 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
998 ],
999 "phase": "public",
1000 "tags": [],
1001 "user": "test"
1002 },
1003 {
1004 "bookmarks": [],
1005 "branch": "default",
1006 "date": [
1007 0.0,
1008 0
1009 ],
1010 "desc": "initial",
1011 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
1012 "parents": [],
1013 "phase": "public",
1014 "tags": [],
1015 "user": "test"
1016 }
1017 ],
1018 "tags": [
1019 {
1020 "date": [
1021 0.0,
1022 0
1023 ],
1024 "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78",
1025 "tag": "tag2"
1026 },
1027 {
1028 "date": [
1029 0.0,
1030 0
1031 ],
1032 "node": "78896eb0e102174ce9278438a95e12543e4367a7",
1033 "tag": "tag1"
1034 }
1035 ]
1036 }
803
1037
804 filediff/{revision}/{path} shows changes to a file in a revision
1038 filediff/{revision}/{path} shows changes to a file in a revision
805
1039
806 $ request json-diff/f8bbb9024b10/foo
1040 $ request json-diff/f8bbb9024b10/foo
807 200 Script output follows
1041 200 Script output follows
808
1042
809 {
1043 {
810 "author": "test",
1044 "author": "test",
811 "children": [],
1045 "children": [],
812 "date": [
1046 "date": [
813 0.0,
1047 0.0,
814 0
1048 0
815 ],
1049 ],
816 "desc": "modify foo",
1050 "desc": "modify foo",
817 "diff": [
1051 "diff": [
818 {
1052 {
819 "blockno": 1,
1053 "blockno": 1,
820 "lines": [
1054 "lines": [
821 {
1055 {
822 "l": "--- a/foo\tThu Jan 01 00:00:00 1970 +0000\n",
1056 "l": "--- a/foo\tThu Jan 01 00:00:00 1970 +0000\n",
823 "n": 1,
1057 "n": 1,
824 "t": "-"
1058 "t": "-"
825 },
1059 },
826 {
1060 {
827 "l": "+++ b/foo\tThu Jan 01 00:00:00 1970 +0000\n",
1061 "l": "+++ b/foo\tThu Jan 01 00:00:00 1970 +0000\n",
828 "n": 2,
1062 "n": 2,
829 "t": "+"
1063 "t": "+"
830 },
1064 },
831 {
1065 {
832 "l": "@@ -1,1 +1,1 @@\n",
1066 "l": "@@ -1,1 +1,1 @@\n",
833 "n": 3,
1067 "n": 3,
834 "t": "@"
1068 "t": "@"
835 },
1069 },
836 {
1070 {
837 "l": "-foo\n",
1071 "l": "-foo\n",
838 "n": 4,
1072 "n": 4,
839 "t": "-"
1073 "t": "-"
840 },
1074 },
841 {
1075 {
842 "l": "+bar\n",
1076 "l": "+bar\n",
843 "n": 5,
1077 "n": 5,
844 "t": "+"
1078 "t": "+"
845 }
1079 }
846 ]
1080 ]
847 }
1081 }
848 ],
1082 ],
849 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
1083 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
850 "parents": [
1084 "parents": [
851 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
1085 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
852 ],
1086 ],
853 "path": "foo"
1087 "path": "foo"
854 }
1088 }
855
1089
856 comparison/{revision}/{path} shows information about before and after for a file
1090 comparison/{revision}/{path} shows information about before and after for a file
857
1091
858 $ request json-comparison/f8bbb9024b10/foo
1092 $ request json-comparison/f8bbb9024b10/foo
859 200 Script output follows
1093 200 Script output follows
860
1094
861 {
1095 {
862 "author": "test",
1096 "author": "test",
863 "children": [],
1097 "children": [],
864 "comparison": [
1098 "comparison": [
865 {
1099 {
866 "lines": [
1100 "lines": [
867 {
1101 {
868 "ll": "foo",
1102 "ll": "foo",
869 "ln": 1,
1103 "ln": 1,
870 "rl": "bar",
1104 "rl": "bar",
871 "rn": 1,
1105 "rn": 1,
872 "t": "replace"
1106 "t": "replace"
873 }
1107 }
874 ]
1108 ]
875 }
1109 }
876 ],
1110 ],
877 "date": [
1111 "date": [
878 0.0,
1112 0.0,
879 0
1113 0
880 ],
1114 ],
881 "desc": "modify foo",
1115 "desc": "modify foo",
882 "leftnode": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
1116 "leftnode": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
883 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
1117 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
884 "parents": [
1118 "parents": [
885 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
1119 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
886 ],
1120 ],
887 "path": "foo",
1121 "path": "foo",
888 "rightnode": "f8bbb9024b10f93cdbb8d940337398291d40dea8"
1122 "rightnode": "f8bbb9024b10f93cdbb8d940337398291d40dea8"
889 }
1123 }
890
1124
891 annotate/{revision}/{path} shows annotations for each line
1125 annotate/{revision}/{path} shows annotations for each line
892
1126
893 $ request json-annotate/f8bbb9024b10/foo
1127 $ request json-annotate/f8bbb9024b10/foo
894 200 Script output follows
1128 200 Script output follows
895
1129
896 {
1130 {
897 "abspath": "foo",
1131 "abspath": "foo",
898 "annotate": [
1132 "annotate": [
899 {
1133 {
900 "abspath": "foo",
1134 "abspath": "foo",
901 "author": "test",
1135 "author": "test",
902 "desc": "modify foo",
1136 "desc": "modify foo",
903 "line": "bar\n",
1137 "line": "bar\n",
904 "lineno": 1,
1138 "lineno": 1,
905 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
1139 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
906 "revdate": [
1140 "revdate": [
907 0.0,
1141 0.0,
908 0
1142 0
909 ],
1143 ],
910 "targetline": 1
1144 "targetline": 1
911 }
1145 }
912 ],
1146 ],
913 "author": "test",
1147 "author": "test",
914 "children": [],
1148 "children": [],
915 "date": [
1149 "date": [
916 0.0,
1150 0.0,
917 0
1151 0
918 ],
1152 ],
919 "desc": "modify foo",
1153 "desc": "modify foo",
920 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
1154 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
921 "parents": [
1155 "parents": [
922 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
1156 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
923 ],
1157 ],
924 "permissions": ""
1158 "permissions": ""
925 }
1159 }
926
1160
927 filelog/{revision}/{path} shows history of a single file
1161 filelog/{revision}/{path} shows history of a single file
928
1162
929 $ request json-filelog/f8bbb9024b10/foo
1163 $ request json-filelog/f8bbb9024b10/foo
930 200 Script output follows
1164 200 Script output follows
931
1165
932 {
1166 {
933 "entries": [
1167 "entries": [
934 {
1168 {
935 "bookmarks": [],
1169 "bookmarks": [],
936 "branch": "default",
1170 "branch": "default",
937 "date": [
1171 "date": [
938 0.0,
1172 0.0,
939 0
1173 0
940 ],
1174 ],
941 "desc": "modify foo",
1175 "desc": "modify foo",
942 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
1176 "node": "f8bbb9024b10f93cdbb8d940337398291d40dea8",
943 "parents": [
1177 "parents": [
944 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
1178 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
945 ],
1179 ],
946 "phase": "public",
1180 "phase": "public",
947 "tags": [],
1181 "tags": [],
948 "user": "test"
1182 "user": "test"
949 },
1183 },
950 {
1184 {
951 "bookmarks": [],
1185 "bookmarks": [],
952 "branch": "default",
1186 "branch": "default",
953 "date": [
1187 "date": [
954 0.0,
1188 0.0,
955 0
1189 0
956 ],
1190 ],
957 "desc": "initial",
1191 "desc": "initial",
958 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
1192 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
959 "parents": [],
1193 "parents": [],
960 "phase": "public",
1194 "phase": "public",
961 "tags": [],
1195 "tags": [],
962 "user": "test"
1196 "user": "test"
963 }
1197 }
964 ]
1198 ]
965 }
1199 }
966
1200
967 $ request json-filelog/cc725e08502a/da/foo
1201 $ request json-filelog/cc725e08502a/da/foo
968 200 Script output follows
1202 200 Script output follows
969
1203
970 {
1204 {
971 "entries": [
1205 "entries": [
972 {
1206 {
973 "bookmarks": [],
1207 "bookmarks": [],
974 "branch": "default",
1208 "branch": "default",
975 "date": [
1209 "date": [
976 0.0,
1210 0.0,
977 0
1211 0
978 ],
1212 ],
979 "desc": "another commit to da/foo",
1213 "desc": "another commit to da/foo",
980 "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78",
1214 "node": "f2890a05fea49bfaf9fb27ed5490894eba32da78",
981 "parents": [
1215 "parents": [
982 "8d7c456572acf3557e8ed8a07286b10c408bcec5"
1216 "8d7c456572acf3557e8ed8a07286b10c408bcec5"
983 ],
1217 ],
984 "phase": "draft",
1218 "phase": "draft",
985 "tags": [
1219 "tags": [
986 "tag2"
1220 "tag2"
987 ],
1221 ],
988 "user": "test"
1222 "user": "test"
989 },
1223 },
990 {
1224 {
991 "bookmarks": [
1225 "bookmarks": [
992 "bookmark1"
1226 "bookmark1"
993 ],
1227 ],
994 "branch": "default",
1228 "branch": "default",
995 "date": [
1229 "date": [
996 0.0,
1230 0.0,
997 0
1231 0
998 ],
1232 ],
999 "desc": "modify da/foo",
1233 "desc": "modify da/foo",
1000 "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5",
1234 "node": "8d7c456572acf3557e8ed8a07286b10c408bcec5",
1001 "parents": [
1235 "parents": [
1002 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
1236 "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e"
1003 ],
1237 ],
1004 "phase": "public",
1238 "phase": "public",
1005 "tags": [],
1239 "tags": [],
1006 "user": "test"
1240 "user": "test"
1007 },
1241 },
1008 {
1242 {
1009 "bookmarks": [],
1243 "bookmarks": [],
1010 "branch": "default",
1244 "branch": "default",
1011 "date": [
1245 "date": [
1012 0.0,
1246 0.0,
1013 0
1247 0
1014 ],
1248 ],
1015 "desc": "initial",
1249 "desc": "initial",
1016 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
1250 "node": "06e557f3edf66faa1ccaba5dd8c203c21cc79f1e",
1017 "parents": [],
1251 "parents": [],
1018 "phase": "public",
1252 "phase": "public",
1019 "tags": [],
1253 "tags": [],
1020 "user": "test"
1254 "user": "test"
1021 }
1255 }
1022 ]
1256 ]
1023 }
1257 }
1024
1258
1025 (archive/ doesn't use templating, so ignore it)
1259 (archive/ doesn't use templating, so ignore it)
1026
1260
1027 (static/ doesn't use templating, so ignore it)
1261 (static/ doesn't use templating, so ignore it)
1028
1262
1029 graph/ shows information that can be used to render a graph of the DAG
1263 graph/ shows information that can be used to render a graph of the DAG
1030
1264
1031 $ request json-graph
1265 $ request json-graph
1032 200 Script output follows
1266 200 Script output follows
1033
1267
1034 "not yet implemented"
1268 "not yet implemented"
1035
1269
1036 help/ shows help topics
1270 help/ shows help topics
1037
1271
1038 $ request json-help
1272 $ request json-help
1039 200 Script output follows
1273 200 Script output follows
1040
1274
1041 {
1275 {
1042 "earlycommands": [
1276 "earlycommands": [
1043 {
1277 {
1044 "summary": "add the specified files on the next commit",
1278 "summary": "add the specified files on the next commit",
1045 "topic": "add"
1279 "topic": "add"
1046 },
1280 },
1047 {
1281 {
1048 "summary": "show changeset information by line for each file",
1282 "summary": "show changeset information by line for each file",
1049 "topic": "annotate"
1283 "topic": "annotate"
1050 },
1284 },
1051 {
1285 {
1052 "summary": "make a copy of an existing repository",
1286 "summary": "make a copy of an existing repository",
1053 "topic": "clone"
1287 "topic": "clone"
1054 },
1288 },
1055 {
1289 {
1056 "summary": "commit the specified files or all outstanding changes",
1290 "summary": "commit the specified files or all outstanding changes",
1057 "topic": "commit"
1291 "topic": "commit"
1058 },
1292 },
1059 {
1293 {
1060 "summary": "diff repository (or selected files)",
1294 "summary": "diff repository (or selected files)",
1061 "topic": "diff"
1295 "topic": "diff"
1062 },
1296 },
1063 {
1297 {
1064 "summary": "dump the header and diffs for one or more changesets",
1298 "summary": "dump the header and diffs for one or more changesets",
1065 "topic": "export"
1299 "topic": "export"
1066 },
1300 },
1067 {
1301 {
1068 "summary": "forget the specified files on the next commit",
1302 "summary": "forget the specified files on the next commit",
1069 "topic": "forget"
1303 "topic": "forget"
1070 },
1304 },
1071 {
1305 {
1072 "summary": "create a new repository in the given directory",
1306 "summary": "create a new repository in the given directory",
1073 "topic": "init"
1307 "topic": "init"
1074 },
1308 },
1075 {
1309 {
1076 "summary": "show revision history of entire repository or files",
1310 "summary": "show revision history of entire repository or files",
1077 "topic": "log"
1311 "topic": "log"
1078 },
1312 },
1079 {
1313 {
1080 "summary": "merge another revision into working directory",
1314 "summary": "merge another revision into working directory",
1081 "topic": "merge"
1315 "topic": "merge"
1082 },
1316 },
1083 {
1317 {
1084 "summary": "pull changes from the specified source",
1318 "summary": "pull changes from the specified source",
1085 "topic": "pull"
1319 "topic": "pull"
1086 },
1320 },
1087 {
1321 {
1088 "summary": "push changes to the specified destination",
1322 "summary": "push changes to the specified destination",
1089 "topic": "push"
1323 "topic": "push"
1090 },
1324 },
1091 {
1325 {
1092 "summary": "remove the specified files on the next commit",
1326 "summary": "remove the specified files on the next commit",
1093 "topic": "remove"
1327 "topic": "remove"
1094 },
1328 },
1095 {
1329 {
1096 "summary": "start stand-alone webserver",
1330 "summary": "start stand-alone webserver",
1097 "topic": "serve"
1331 "topic": "serve"
1098 },
1332 },
1099 {
1333 {
1100 "summary": "show changed files in the working directory",
1334 "summary": "show changed files in the working directory",
1101 "topic": "status"
1335 "topic": "status"
1102 },
1336 },
1103 {
1337 {
1104 "summary": "summarize working directory state",
1338 "summary": "summarize working directory state",
1105 "topic": "summary"
1339 "topic": "summary"
1106 },
1340 },
1107 {
1341 {
1108 "summary": "update working directory (or switch revisions)",
1342 "summary": "update working directory (or switch revisions)",
1109 "topic": "update"
1343 "topic": "update"
1110 }
1344 }
1111 ],
1345 ],
1112 "othercommands": [
1346 "othercommands": [
1113 {
1347 {
1114 "summary": "add all new files, delete all missing files",
1348 "summary": "add all new files, delete all missing files",
1115 "topic": "addremove"
1349 "topic": "addremove"
1116 },
1350 },
1117 {
1351 {
1118 "summary": "create an unversioned archive of a repository revision",
1352 "summary": "create an unversioned archive of a repository revision",
1119 "topic": "archive"
1353 "topic": "archive"
1120 },
1354 },
1121 {
1355 {
1122 "summary": "reverse effect of earlier changeset",
1356 "summary": "reverse effect of earlier changeset",
1123 "topic": "backout"
1357 "topic": "backout"
1124 },
1358 },
1125 {
1359 {
1126 "summary": "subdivision search of changesets",
1360 "summary": "subdivision search of changesets",
1127 "topic": "bisect"
1361 "topic": "bisect"
1128 },
1362 },
1129 {
1363 {
1130 "summary": "create a new bookmark or list existing bookmarks",
1364 "summary": "create a new bookmark or list existing bookmarks",
1131 "topic": "bookmarks"
1365 "topic": "bookmarks"
1132 },
1366 },
1133 {
1367 {
1134 "summary": "set or show the current branch name",
1368 "summary": "set or show the current branch name",
1135 "topic": "branch"
1369 "topic": "branch"
1136 },
1370 },
1137 {
1371 {
1138 "summary": "list repository named branches",
1372 "summary": "list repository named branches",
1139 "topic": "branches"
1373 "topic": "branches"
1140 },
1374 },
1141 {
1375 {
1142 "summary": "create a changegroup file",
1376 "summary": "create a changegroup file",
1143 "topic": "bundle"
1377 "topic": "bundle"
1144 },
1378 },
1145 {
1379 {
1146 "summary": "output the current or given revision of files",
1380 "summary": "output the current or given revision of files",
1147 "topic": "cat"
1381 "topic": "cat"
1148 },
1382 },
1149 {
1383 {
1150 "summary": "show combined config settings from all hgrc files",
1384 "summary": "show combined config settings from all hgrc files",
1151 "topic": "config"
1385 "topic": "config"
1152 },
1386 },
1153 {
1387 {
1154 "summary": "mark files as copied for the next commit",
1388 "summary": "mark files as copied for the next commit",
1155 "topic": "copy"
1389 "topic": "copy"
1156 },
1390 },
1157 {
1391 {
1158 "summary": "list tracked files",
1392 "summary": "list tracked files",
1159 "topic": "files"
1393 "topic": "files"
1160 },
1394 },
1161 {
1395 {
1162 "summary": "copy changes from other branches onto the current branch",
1396 "summary": "copy changes from other branches onto the current branch",
1163 "topic": "graft"
1397 "topic": "graft"
1164 },
1398 },
1165 {
1399 {
1166 "summary": "search for a pattern in specified files and revisions",
1400 "summary": "search for a pattern in specified files and revisions",
1167 "topic": "grep"
1401 "topic": "grep"
1168 },
1402 },
1169 {
1403 {
1170 "summary": "show branch heads",
1404 "summary": "show branch heads",
1171 "topic": "heads"
1405 "topic": "heads"
1172 },
1406 },
1173 {
1407 {
1174 "summary": "show help for a given topic or a help overview",
1408 "summary": "show help for a given topic or a help overview",
1175 "topic": "help"
1409 "topic": "help"
1176 },
1410 },
1177 {
1411 {
1178 "summary": "identify the working directory or specified revision",
1412 "summary": "identify the working directory or specified revision",
1179 "topic": "identify"
1413 "topic": "identify"
1180 },
1414 },
1181 {
1415 {
1182 "summary": "import an ordered set of patches",
1416 "summary": "import an ordered set of patches",
1183 "topic": "import"
1417 "topic": "import"
1184 },
1418 },
1185 {
1419 {
1186 "summary": "show new changesets found in source",
1420 "summary": "show new changesets found in source",
1187 "topic": "incoming"
1421 "topic": "incoming"
1188 },
1422 },
1189 {
1423 {
1190 "summary": "output the current or given revision of the project manifest",
1424 "summary": "output the current or given revision of the project manifest",
1191 "topic": "manifest"
1425 "topic": "manifest"
1192 },
1426 },
1193 {
1427 {
1194 "summary": "show changesets not found in the destination",
1428 "summary": "show changesets not found in the destination",
1195 "topic": "outgoing"
1429 "topic": "outgoing"
1196 },
1430 },
1197 {
1431 {
1198 "summary": "show aliases for remote repositories",
1432 "summary": "show aliases for remote repositories",
1199 "topic": "paths"
1433 "topic": "paths"
1200 },
1434 },
1201 {
1435 {
1202 "summary": "set or show the current phase name",
1436 "summary": "set or show the current phase name",
1203 "topic": "phase"
1437 "topic": "phase"
1204 },
1438 },
1205 {
1439 {
1206 "summary": "roll back an interrupted transaction",
1440 "summary": "roll back an interrupted transaction",
1207 "topic": "recover"
1441 "topic": "recover"
1208 },
1442 },
1209 {
1443 {
1210 "summary": "rename files; equivalent of copy + remove",
1444 "summary": "rename files; equivalent of copy + remove",
1211 "topic": "rename"
1445 "topic": "rename"
1212 },
1446 },
1213 {
1447 {
1214 "summary": "redo merges or set/view the merge status of files",
1448 "summary": "redo merges or set/view the merge status of files",
1215 "topic": "resolve"
1449 "topic": "resolve"
1216 },
1450 },
1217 {
1451 {
1218 "summary": "restore files to their checkout state",
1452 "summary": "restore files to their checkout state",
1219 "topic": "revert"
1453 "topic": "revert"
1220 },
1454 },
1221 {
1455 {
1222 "summary": "print the root (top) of the current working directory",
1456 "summary": "print the root (top) of the current working directory",
1223 "topic": "root"
1457 "topic": "root"
1224 },
1458 },
1225 {
1459 {
1226 "summary": "add one or more tags for the current or given revision",
1460 "summary": "add one or more tags for the current or given revision",
1227 "topic": "tag"
1461 "topic": "tag"
1228 },
1462 },
1229 {
1463 {
1230 "summary": "list repository tags",
1464 "summary": "list repository tags",
1231 "topic": "tags"
1465 "topic": "tags"
1232 },
1466 },
1233 {
1467 {
1234 "summary": "apply one or more changegroup files",
1468 "summary": "apply one or more changegroup files",
1235 "topic": "unbundle"
1469 "topic": "unbundle"
1236 },
1470 },
1237 {
1471 {
1238 "summary": "verify the integrity of the repository",
1472 "summary": "verify the integrity of the repository",
1239 "topic": "verify"
1473 "topic": "verify"
1240 },
1474 },
1241 {
1475 {
1242 "summary": "output version and copyright information",
1476 "summary": "output version and copyright information",
1243 "topic": "version"
1477 "topic": "version"
1244 }
1478 }
1245 ],
1479 ],
1246 "topics": [
1480 "topics": [
1247 {
1481 {
1248 "summary": "Configuration Files",
1482 "summary": "Configuration Files",
1249 "topic": "config"
1483 "topic": "config"
1250 },
1484 },
1251 {
1485 {
1252 "summary": "Date Formats",
1486 "summary": "Date Formats",
1253 "topic": "dates"
1487 "topic": "dates"
1254 },
1488 },
1255 {
1489 {
1256 "summary": "Diff Formats",
1490 "summary": "Diff Formats",
1257 "topic": "diffs"
1491 "topic": "diffs"
1258 },
1492 },
1259 {
1493 {
1260 "summary": "Environment Variables",
1494 "summary": "Environment Variables",
1261 "topic": "environment"
1495 "topic": "environment"
1262 },
1496 },
1263 {
1497 {
1264 "summary": "Using Additional Features",
1498 "summary": "Using Additional Features",
1265 "topic": "extensions"
1499 "topic": "extensions"
1266 },
1500 },
1267 {
1501 {
1268 "summary": "Specifying File Sets",
1502 "summary": "Specifying File Sets",
1269 "topic": "filesets"
1503 "topic": "filesets"
1270 },
1504 },
1271 {
1505 {
1272 "summary": "Glossary",
1506 "summary": "Glossary",
1273 "topic": "glossary"
1507 "topic": "glossary"
1274 },
1508 },
1275 {
1509 {
1276 "summary": "Syntax for Mercurial Ignore Files",
1510 "summary": "Syntax for Mercurial Ignore Files",
1277 "topic": "hgignore"
1511 "topic": "hgignore"
1278 },
1512 },
1279 {
1513 {
1280 "summary": "Configuring hgweb",
1514 "summary": "Configuring hgweb",
1281 "topic": "hgweb"
1515 "topic": "hgweb"
1282 },
1516 },
1283 {
1517 {
1284 "summary": "Technical implementation topics",
1518 "summary": "Technical implementation topics",
1285 "topic": "internals"
1519 "topic": "internals"
1286 },
1520 },
1287 {
1521 {
1288 "summary": "Merge Tools",
1522 "summary": "Merge Tools",
1289 "topic": "merge-tools"
1523 "topic": "merge-tools"
1290 },
1524 },
1291 {
1525 {
1292 "summary": "Specifying Multiple Revisions",
1526 "summary": "Specifying Multiple Revisions",
1293 "topic": "multirevs"
1527 "topic": "multirevs"
1294 },
1528 },
1295 {
1529 {
1296 "summary": "File Name Patterns",
1530 "summary": "File Name Patterns",
1297 "topic": "patterns"
1531 "topic": "patterns"
1298 },
1532 },
1299 {
1533 {
1300 "summary": "Working with Phases",
1534 "summary": "Working with Phases",
1301 "topic": "phases"
1535 "topic": "phases"
1302 },
1536 },
1303 {
1537 {
1304 "summary": "Specifying Single Revisions",
1538 "summary": "Specifying Single Revisions",
1305 "topic": "revisions"
1539 "topic": "revisions"
1306 },
1540 },
1307 {
1541 {
1308 "summary": "Specifying Revision Sets",
1542 "summary": "Specifying Revision Sets",
1309 "topic": "revsets"
1543 "topic": "revsets"
1310 },
1544 },
1311 {
1545 {
1312 "summary": "Using Mercurial from scripts and automation",
1546 "summary": "Using Mercurial from scripts and automation",
1313 "topic": "scripting"
1547 "topic": "scripting"
1314 },
1548 },
1315 {
1549 {
1316 "summary": "Subrepositories",
1550 "summary": "Subrepositories",
1317 "topic": "subrepos"
1551 "topic": "subrepos"
1318 },
1552 },
1319 {
1553 {
1320 "summary": "Template Usage",
1554 "summary": "Template Usage",
1321 "topic": "templating"
1555 "topic": "templating"
1322 },
1556 },
1323 {
1557 {
1324 "summary": "URL Paths",
1558 "summary": "URL Paths",
1325 "topic": "urls"
1559 "topic": "urls"
1326 }
1560 }
1327 ]
1561 ]
1328 }
1562 }
1329
1563
1330 help/{topic} shows an individual help topic
1564 help/{topic} shows an individual help topic
1331
1565
1332 $ request json-help/phases
1566 $ request json-help/phases
1333 200 Script output follows
1567 200 Script output follows
1334
1568
1335 {
1569 {
1336 "rawdoc": "Working with Phases\n*", (glob)
1570 "rawdoc": "Working with Phases\n*", (glob)
1337 "topic": "phases"
1571 "topic": "phases"
1338 }
1572 }
General Comments 0
You need to be logged in to leave comments. Login now