##// END OF EJS Templates
hgweb: handle a "linerange" request parameter in filelog command...
Denis Laxalde -
r31665:5e6d4451 default
parent child Browse files
Show More
@@ -1,1343 +1,1372 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 context,
31 encoding,
32 encoding,
32 error,
33 error,
33 graphmod,
34 graphmod,
34 revset,
35 revset,
35 revsetlang,
36 revsetlang,
36 scmutil,
37 scmutil,
37 smartset,
38 smartset,
38 templatefilters,
39 templatefilters,
39 templater,
40 templater,
40 util,
41 util,
41 )
42 )
42
43
43 from . import (
44 from . import (
44 webutil,
45 webutil,
45 )
46 )
46
47
47 __all__ = []
48 __all__ = []
48 commands = {}
49 commands = {}
49
50
50 class webcommand(object):
51 class webcommand(object):
51 """Decorator used to register a web command handler.
52 """Decorator used to register a web command handler.
52
53
53 The decorator takes as its positional arguments the name/path the
54 The decorator takes as its positional arguments the name/path the
54 command should be accessible under.
55 command should be accessible under.
55
56
56 Usage:
57 Usage:
57
58
58 @webcommand('mycommand')
59 @webcommand('mycommand')
59 def mycommand(web, req, tmpl):
60 def mycommand(web, req, tmpl):
60 pass
61 pass
61 """
62 """
62
63
63 def __init__(self, name):
64 def __init__(self, name):
64 self.name = name
65 self.name = name
65
66
66 def __call__(self, func):
67 def __call__(self, func):
67 __all__.append(self.name)
68 __all__.append(self.name)
68 commands[self.name] = func
69 commands[self.name] = func
69 return func
70 return func
70
71
71 @webcommand('log')
72 @webcommand('log')
72 def log(web, req, tmpl):
73 def log(web, req, tmpl):
73 """
74 """
74 /log[/{revision}[/{path}]]
75 /log[/{revision}[/{path}]]
75 --------------------------
76 --------------------------
76
77
77 Show repository or file history.
78 Show repository or file history.
78
79
79 For URLs of the form ``/log/{revision}``, a list of changesets starting at
80 For URLs of the form ``/log/{revision}``, a list of changesets starting at
80 the specified changeset identifier is shown. If ``{revision}`` is not
81 the specified changeset identifier is shown. If ``{revision}`` is not
81 defined, the default is ``tip``. This form is equivalent to the
82 defined, the default is ``tip``. This form is equivalent to the
82 ``changelog`` handler.
83 ``changelog`` handler.
83
84
84 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
85 For URLs of the form ``/log/{revision}/{file}``, the history for a specific
85 file will be shown. This form is equivalent to the ``filelog`` handler.
86 file will be shown. This form is equivalent to the ``filelog`` handler.
86 """
87 """
87
88
88 if 'file' in req.form and req.form['file'][0]:
89 if 'file' in req.form and req.form['file'][0]:
89 return filelog(web, req, tmpl)
90 return filelog(web, req, tmpl)
90 else:
91 else:
91 return changelog(web, req, tmpl)
92 return changelog(web, req, tmpl)
92
93
93 @webcommand('rawfile')
94 @webcommand('rawfile')
94 def rawfile(web, req, tmpl):
95 def rawfile(web, req, tmpl):
95 guessmime = web.configbool('web', 'guessmime', False)
96 guessmime = web.configbool('web', 'guessmime', False)
96
97
97 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
98 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
98 if not path:
99 if not path:
99 content = manifest(web, req, tmpl)
100 content = manifest(web, req, tmpl)
100 req.respond(HTTP_OK, web.ctype)
101 req.respond(HTTP_OK, web.ctype)
101 return content
102 return content
102
103
103 try:
104 try:
104 fctx = webutil.filectx(web.repo, req)
105 fctx = webutil.filectx(web.repo, req)
105 except error.LookupError as inst:
106 except error.LookupError as inst:
106 try:
107 try:
107 content = manifest(web, req, tmpl)
108 content = manifest(web, req, tmpl)
108 req.respond(HTTP_OK, web.ctype)
109 req.respond(HTTP_OK, web.ctype)
109 return content
110 return content
110 except ErrorResponse:
111 except ErrorResponse:
111 raise inst
112 raise inst
112
113
113 path = fctx.path()
114 path = fctx.path()
114 text = fctx.data()
115 text = fctx.data()
115 mt = 'application/binary'
116 mt = 'application/binary'
116 if guessmime:
117 if guessmime:
117 mt = mimetypes.guess_type(path)[0]
118 mt = mimetypes.guess_type(path)[0]
118 if mt is None:
119 if mt is None:
119 if util.binary(text):
120 if util.binary(text):
120 mt = 'application/binary'
121 mt = 'application/binary'
121 else:
122 else:
122 mt = 'text/plain'
123 mt = 'text/plain'
123 if mt.startswith('text/'):
124 if mt.startswith('text/'):
124 mt += '; charset="%s"' % encoding.encoding
125 mt += '; charset="%s"' % encoding.encoding
125
126
126 req.respond(HTTP_OK, mt, path, body=text)
127 req.respond(HTTP_OK, mt, path, body=text)
127 return []
128 return []
128
129
129 def _filerevision(web, req, tmpl, fctx):
130 def _filerevision(web, req, tmpl, fctx):
130 f = fctx.path()
131 f = fctx.path()
131 text = fctx.data()
132 text = fctx.data()
132 parity = paritygen(web.stripecount)
133 parity = paritygen(web.stripecount)
133
134
134 if util.binary(text):
135 if util.binary(text):
135 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
136 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
136 text = '(binary:%s)' % mt
137 text = '(binary:%s)' % mt
137
138
138 def lines():
139 def lines():
139 for lineno, t in enumerate(text.splitlines(True)):
140 for lineno, t in enumerate(text.splitlines(True)):
140 yield {"line": t,
141 yield {"line": t,
141 "lineid": "l%d" % (lineno + 1),
142 "lineid": "l%d" % (lineno + 1),
142 "linenumber": "% 6d" % (lineno + 1),
143 "linenumber": "% 6d" % (lineno + 1),
143 "parity": next(parity)}
144 "parity": next(parity)}
144
145
145 return tmpl("filerevision",
146 return tmpl("filerevision",
146 file=f,
147 file=f,
147 path=webutil.up(f),
148 path=webutil.up(f),
148 text=lines(),
149 text=lines(),
149 symrev=webutil.symrevorshortnode(req, fctx),
150 symrev=webutil.symrevorshortnode(req, fctx),
150 rename=webutil.renamelink(fctx),
151 rename=webutil.renamelink(fctx),
151 permissions=fctx.manifest().flags(f),
152 permissions=fctx.manifest().flags(f),
152 **webutil.commonentry(web.repo, fctx))
153 **webutil.commonentry(web.repo, fctx))
153
154
154 @webcommand('file')
155 @webcommand('file')
155 def file(web, req, tmpl):
156 def file(web, req, tmpl):
156 """
157 """
157 /file/{revision}[/{path}]
158 /file/{revision}[/{path}]
158 -------------------------
159 -------------------------
159
160
160 Show information about a directory or file in the repository.
161 Show information about a directory or file in the repository.
161
162
162 Info about the ``path`` given as a URL parameter will be rendered.
163 Info about the ``path`` given as a URL parameter will be rendered.
163
164
164 If ``path`` is a directory, information about the entries in that
165 If ``path`` is a directory, information about the entries in that
165 directory will be rendered. This form is equivalent to the ``manifest``
166 directory will be rendered. This form is equivalent to the ``manifest``
166 handler.
167 handler.
167
168
168 If ``path`` is a file, information about that file will be shown via
169 If ``path`` is a file, information about that file will be shown via
169 the ``filerevision`` template.
170 the ``filerevision`` template.
170
171
171 If ``path`` is not defined, information about the root directory will
172 If ``path`` is not defined, information about the root directory will
172 be rendered.
173 be rendered.
173 """
174 """
174 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
175 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
175 if not path:
176 if not path:
176 return manifest(web, req, tmpl)
177 return manifest(web, req, tmpl)
177 try:
178 try:
178 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
179 return _filerevision(web, req, tmpl, webutil.filectx(web.repo, req))
179 except error.LookupError as inst:
180 except error.LookupError as inst:
180 try:
181 try:
181 return manifest(web, req, tmpl)
182 return manifest(web, req, tmpl)
182 except ErrorResponse:
183 except ErrorResponse:
183 raise inst
184 raise inst
184
185
185 def _search(web, req, tmpl):
186 def _search(web, req, tmpl):
186 MODE_REVISION = 'rev'
187 MODE_REVISION = 'rev'
187 MODE_KEYWORD = 'keyword'
188 MODE_KEYWORD = 'keyword'
188 MODE_REVSET = 'revset'
189 MODE_REVSET = 'revset'
189
190
190 def revsearch(ctx):
191 def revsearch(ctx):
191 yield ctx
192 yield ctx
192
193
193 def keywordsearch(query):
194 def keywordsearch(query):
194 lower = encoding.lower
195 lower = encoding.lower
195 qw = lower(query).split()
196 qw = lower(query).split()
196
197
197 def revgen():
198 def revgen():
198 cl = web.repo.changelog
199 cl = web.repo.changelog
199 for i in xrange(len(web.repo) - 1, 0, -100):
200 for i in xrange(len(web.repo) - 1, 0, -100):
200 l = []
201 l = []
201 for j in cl.revs(max(0, i - 99), i):
202 for j in cl.revs(max(0, i - 99), i):
202 ctx = web.repo[j]
203 ctx = web.repo[j]
203 l.append(ctx)
204 l.append(ctx)
204 l.reverse()
205 l.reverse()
205 for e in l:
206 for e in l:
206 yield e
207 yield e
207
208
208 for ctx in revgen():
209 for ctx in revgen():
209 miss = 0
210 miss = 0
210 for q in qw:
211 for q in qw:
211 if not (q in lower(ctx.user()) or
212 if not (q in lower(ctx.user()) or
212 q in lower(ctx.description()) or
213 q in lower(ctx.description()) or
213 q in lower(" ".join(ctx.files()))):
214 q in lower(" ".join(ctx.files()))):
214 miss = 1
215 miss = 1
215 break
216 break
216 if miss:
217 if miss:
217 continue
218 continue
218
219
219 yield ctx
220 yield ctx
220
221
221 def revsetsearch(revs):
222 def revsetsearch(revs):
222 for r in revs:
223 for r in revs:
223 yield web.repo[r]
224 yield web.repo[r]
224
225
225 searchfuncs = {
226 searchfuncs = {
226 MODE_REVISION: (revsearch, 'exact revision search'),
227 MODE_REVISION: (revsearch, 'exact revision search'),
227 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
228 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
228 MODE_REVSET: (revsetsearch, 'revset expression search'),
229 MODE_REVSET: (revsetsearch, 'revset expression search'),
229 }
230 }
230
231
231 def getsearchmode(query):
232 def getsearchmode(query):
232 try:
233 try:
233 ctx = web.repo[query]
234 ctx = web.repo[query]
234 except (error.RepoError, error.LookupError):
235 except (error.RepoError, error.LookupError):
235 # query is not an exact revision pointer, need to
236 # query is not an exact revision pointer, need to
236 # decide if it's a revset expression or keywords
237 # decide if it's a revset expression or keywords
237 pass
238 pass
238 else:
239 else:
239 return MODE_REVISION, ctx
240 return MODE_REVISION, ctx
240
241
241 revdef = 'reverse(%s)' % query
242 revdef = 'reverse(%s)' % query
242 try:
243 try:
243 tree = revsetlang.parse(revdef)
244 tree = revsetlang.parse(revdef)
244 except error.ParseError:
245 except error.ParseError:
245 # can't parse to a revset tree
246 # can't parse to a revset tree
246 return MODE_KEYWORD, query
247 return MODE_KEYWORD, query
247
248
248 if revsetlang.depth(tree) <= 2:
249 if revsetlang.depth(tree) <= 2:
249 # no revset syntax used
250 # no revset syntax used
250 return MODE_KEYWORD, query
251 return MODE_KEYWORD, query
251
252
252 if any((token, (value or '')[:3]) == ('string', 're:')
253 if any((token, (value or '')[:3]) == ('string', 're:')
253 for token, value, pos in revsetlang.tokenize(revdef)):
254 for token, value, pos in revsetlang.tokenize(revdef)):
254 return MODE_KEYWORD, query
255 return MODE_KEYWORD, query
255
256
256 funcsused = revsetlang.funcsused(tree)
257 funcsused = revsetlang.funcsused(tree)
257 if not funcsused.issubset(revset.safesymbols):
258 if not funcsused.issubset(revset.safesymbols):
258 return MODE_KEYWORD, query
259 return MODE_KEYWORD, query
259
260
260 mfunc = revset.match(web.repo.ui, revdef)
261 mfunc = revset.match(web.repo.ui, revdef)
261 try:
262 try:
262 revs = mfunc(web.repo)
263 revs = mfunc(web.repo)
263 return MODE_REVSET, revs
264 return MODE_REVSET, revs
264 # ParseError: wrongly placed tokens, wrongs arguments, etc
265 # ParseError: wrongly placed tokens, wrongs arguments, etc
265 # RepoLookupError: no such revision, e.g. in 'revision:'
266 # RepoLookupError: no such revision, e.g. in 'revision:'
266 # Abort: bookmark/tag not exists
267 # Abort: bookmark/tag not exists
267 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
268 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
268 except (error.ParseError, error.RepoLookupError, error.Abort,
269 except (error.ParseError, error.RepoLookupError, error.Abort,
269 LookupError):
270 LookupError):
270 return MODE_KEYWORD, query
271 return MODE_KEYWORD, query
271
272
272 def changelist(**map):
273 def changelist(**map):
273 count = 0
274 count = 0
274
275
275 for ctx in searchfunc[0](funcarg):
276 for ctx in searchfunc[0](funcarg):
276 count += 1
277 count += 1
277 n = ctx.node()
278 n = ctx.node()
278 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
279 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
279 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
280 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
280
281
281 yield tmpl('searchentry',
282 yield tmpl('searchentry',
282 parity=next(parity),
283 parity=next(parity),
283 changelogtag=showtags,
284 changelogtag=showtags,
284 files=files,
285 files=files,
285 **webutil.commonentry(web.repo, ctx))
286 **webutil.commonentry(web.repo, ctx))
286
287
287 if count >= revcount:
288 if count >= revcount:
288 break
289 break
289
290
290 query = req.form['rev'][0]
291 query = req.form['rev'][0]
291 revcount = web.maxchanges
292 revcount = web.maxchanges
292 if 'revcount' in req.form:
293 if 'revcount' in req.form:
293 try:
294 try:
294 revcount = int(req.form.get('revcount', [revcount])[0])
295 revcount = int(req.form.get('revcount', [revcount])[0])
295 revcount = max(revcount, 1)
296 revcount = max(revcount, 1)
296 tmpl.defaults['sessionvars']['revcount'] = revcount
297 tmpl.defaults['sessionvars']['revcount'] = revcount
297 except ValueError:
298 except ValueError:
298 pass
299 pass
299
300
300 lessvars = copy.copy(tmpl.defaults['sessionvars'])
301 lessvars = copy.copy(tmpl.defaults['sessionvars'])
301 lessvars['revcount'] = max(revcount / 2, 1)
302 lessvars['revcount'] = max(revcount / 2, 1)
302 lessvars['rev'] = query
303 lessvars['rev'] = query
303 morevars = copy.copy(tmpl.defaults['sessionvars'])
304 morevars = copy.copy(tmpl.defaults['sessionvars'])
304 morevars['revcount'] = revcount * 2
305 morevars['revcount'] = revcount * 2
305 morevars['rev'] = query
306 morevars['rev'] = query
306
307
307 mode, funcarg = getsearchmode(query)
308 mode, funcarg = getsearchmode(query)
308
309
309 if 'forcekw' in req.form:
310 if 'forcekw' in req.form:
310 showforcekw = ''
311 showforcekw = ''
311 showunforcekw = searchfuncs[mode][1]
312 showunforcekw = searchfuncs[mode][1]
312 mode = MODE_KEYWORD
313 mode = MODE_KEYWORD
313 funcarg = query
314 funcarg = query
314 else:
315 else:
315 if mode != MODE_KEYWORD:
316 if mode != MODE_KEYWORD:
316 showforcekw = searchfuncs[MODE_KEYWORD][1]
317 showforcekw = searchfuncs[MODE_KEYWORD][1]
317 else:
318 else:
318 showforcekw = ''
319 showforcekw = ''
319 showunforcekw = ''
320 showunforcekw = ''
320
321
321 searchfunc = searchfuncs[mode]
322 searchfunc = searchfuncs[mode]
322
323
323 tip = web.repo['tip']
324 tip = web.repo['tip']
324 parity = paritygen(web.stripecount)
325 parity = paritygen(web.stripecount)
325
326
326 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
327 return tmpl('search', query=query, node=tip.hex(), symrev='tip',
327 entries=changelist, archives=web.archivelist("tip"),
328 entries=changelist, archives=web.archivelist("tip"),
328 morevars=morevars, lessvars=lessvars,
329 morevars=morevars, lessvars=lessvars,
329 modedesc=searchfunc[1],
330 modedesc=searchfunc[1],
330 showforcekw=showforcekw, showunforcekw=showunforcekw)
331 showforcekw=showforcekw, showunforcekw=showunforcekw)
331
332
332 @webcommand('changelog')
333 @webcommand('changelog')
333 def changelog(web, req, tmpl, shortlog=False):
334 def changelog(web, req, tmpl, shortlog=False):
334 """
335 """
335 /changelog[/{revision}]
336 /changelog[/{revision}]
336 -----------------------
337 -----------------------
337
338
338 Show information about multiple changesets.
339 Show information about multiple changesets.
339
340
340 If the optional ``revision`` URL argument is absent, information about
341 If the optional ``revision`` URL argument is absent, information about
341 all changesets starting at ``tip`` will be rendered. If the ``revision``
342 all changesets starting at ``tip`` will be rendered. If the ``revision``
342 argument is present, changesets will be shown starting from the specified
343 argument is present, changesets will be shown starting from the specified
343 revision.
344 revision.
344
345
345 If ``revision`` is absent, the ``rev`` query string argument may be
346 If ``revision`` is absent, the ``rev`` query string argument may be
346 defined. This will perform a search for changesets.
347 defined. This will perform a search for changesets.
347
348
348 The argument for ``rev`` can be a single revision, a revision set,
349 The argument for ``rev`` can be a single revision, a revision set,
349 or a literal keyword to search for in changeset data (equivalent to
350 or a literal keyword to search for in changeset data (equivalent to
350 :hg:`log -k`).
351 :hg:`log -k`).
351
352
352 The ``revcount`` query string argument defines the maximum numbers of
353 The ``revcount`` query string argument defines the maximum numbers of
353 changesets to render.
354 changesets to render.
354
355
355 For non-searches, the ``changelog`` template will be rendered.
356 For non-searches, the ``changelog`` template will be rendered.
356 """
357 """
357
358
358 query = ''
359 query = ''
359 if 'node' in req.form:
360 if 'node' in req.form:
360 ctx = webutil.changectx(web.repo, req)
361 ctx = webutil.changectx(web.repo, req)
361 symrev = webutil.symrevorshortnode(req, ctx)
362 symrev = webutil.symrevorshortnode(req, ctx)
362 elif 'rev' in req.form:
363 elif 'rev' in req.form:
363 return _search(web, req, tmpl)
364 return _search(web, req, tmpl)
364 else:
365 else:
365 ctx = web.repo['tip']
366 ctx = web.repo['tip']
366 symrev = 'tip'
367 symrev = 'tip'
367
368
368 def changelist():
369 def changelist():
369 revs = []
370 revs = []
370 if pos != -1:
371 if pos != -1:
371 revs = web.repo.changelog.revs(pos, 0)
372 revs = web.repo.changelog.revs(pos, 0)
372 curcount = 0
373 curcount = 0
373 for rev in revs:
374 for rev in revs:
374 curcount += 1
375 curcount += 1
375 if curcount > revcount + 1:
376 if curcount > revcount + 1:
376 break
377 break
377
378
378 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
379 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
379 entry['parity'] = next(parity)
380 entry['parity'] = next(parity)
380 yield entry
381 yield entry
381
382
382 if shortlog:
383 if shortlog:
383 revcount = web.maxshortchanges
384 revcount = web.maxshortchanges
384 else:
385 else:
385 revcount = web.maxchanges
386 revcount = web.maxchanges
386
387
387 if 'revcount' in req.form:
388 if 'revcount' in req.form:
388 try:
389 try:
389 revcount = int(req.form.get('revcount', [revcount])[0])
390 revcount = int(req.form.get('revcount', [revcount])[0])
390 revcount = max(revcount, 1)
391 revcount = max(revcount, 1)
391 tmpl.defaults['sessionvars']['revcount'] = revcount
392 tmpl.defaults['sessionvars']['revcount'] = revcount
392 except ValueError:
393 except ValueError:
393 pass
394 pass
394
395
395 lessvars = copy.copy(tmpl.defaults['sessionvars'])
396 lessvars = copy.copy(tmpl.defaults['sessionvars'])
396 lessvars['revcount'] = max(revcount / 2, 1)
397 lessvars['revcount'] = max(revcount / 2, 1)
397 morevars = copy.copy(tmpl.defaults['sessionvars'])
398 morevars = copy.copy(tmpl.defaults['sessionvars'])
398 morevars['revcount'] = revcount * 2
399 morevars['revcount'] = revcount * 2
399
400
400 count = len(web.repo)
401 count = len(web.repo)
401 pos = ctx.rev()
402 pos = ctx.rev()
402 parity = paritygen(web.stripecount)
403 parity = paritygen(web.stripecount)
403
404
404 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
405 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
405
406
406 entries = list(changelist())
407 entries = list(changelist())
407 latestentry = entries[:1]
408 latestentry = entries[:1]
408 if len(entries) > revcount:
409 if len(entries) > revcount:
409 nextentry = entries[-1:]
410 nextentry = entries[-1:]
410 entries = entries[:-1]
411 entries = entries[:-1]
411 else:
412 else:
412 nextentry = []
413 nextentry = []
413
414
414 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
415 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
415 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
416 node=ctx.hex(), rev=pos, symrev=symrev, changesets=count,
416 entries=entries,
417 entries=entries,
417 latestentry=latestentry, nextentry=nextentry,
418 latestentry=latestentry, nextentry=nextentry,
418 archives=web.archivelist("tip"), revcount=revcount,
419 archives=web.archivelist("tip"), revcount=revcount,
419 morevars=morevars, lessvars=lessvars, query=query)
420 morevars=morevars, lessvars=lessvars, query=query)
420
421
421 @webcommand('shortlog')
422 @webcommand('shortlog')
422 def shortlog(web, req, tmpl):
423 def shortlog(web, req, tmpl):
423 """
424 """
424 /shortlog
425 /shortlog
425 ---------
426 ---------
426
427
427 Show basic information about a set of changesets.
428 Show basic information about a set of changesets.
428
429
429 This accepts the same parameters as the ``changelog`` handler. The only
430 This accepts the same parameters as the ``changelog`` handler. The only
430 difference is the ``shortlog`` template will be rendered instead of the
431 difference is the ``shortlog`` template will be rendered instead of the
431 ``changelog`` template.
432 ``changelog`` template.
432 """
433 """
433 return changelog(web, req, tmpl, shortlog=True)
434 return changelog(web, req, tmpl, shortlog=True)
434
435
435 @webcommand('changeset')
436 @webcommand('changeset')
436 def changeset(web, req, tmpl):
437 def changeset(web, req, tmpl):
437 """
438 """
438 /changeset[/{revision}]
439 /changeset[/{revision}]
439 -----------------------
440 -----------------------
440
441
441 Show information about a single changeset.
442 Show information about a single changeset.
442
443
443 A URL path argument is the changeset identifier to show. See ``hg help
444 A URL path argument is the changeset identifier to show. See ``hg help
444 revisions`` for possible values. If not defined, the ``tip`` changeset
445 revisions`` for possible values. If not defined, the ``tip`` changeset
445 will be shown.
446 will be shown.
446
447
447 The ``changeset`` template is rendered. Contents of the ``changesettag``,
448 The ``changeset`` template is rendered. Contents of the ``changesettag``,
448 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
449 ``changesetbookmark``, ``filenodelink``, ``filenolink``, and the many
449 templates related to diffs may all be used to produce the output.
450 templates related to diffs may all be used to produce the output.
450 """
451 """
451 ctx = webutil.changectx(web.repo, req)
452 ctx = webutil.changectx(web.repo, req)
452
453
453 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
454 return tmpl('changeset', **webutil.changesetentry(web, req, tmpl, ctx))
454
455
455 rev = webcommand('rev')(changeset)
456 rev = webcommand('rev')(changeset)
456
457
457 def decodepath(path):
458 def decodepath(path):
458 """Hook for mapping a path in the repository to a path in the
459 """Hook for mapping a path in the repository to a path in the
459 working copy.
460 working copy.
460
461
461 Extensions (e.g., largefiles) can override this to remap files in
462 Extensions (e.g., largefiles) can override this to remap files in
462 the virtual file system presented by the manifest command below."""
463 the virtual file system presented by the manifest command below."""
463 return path
464 return path
464
465
465 @webcommand('manifest')
466 @webcommand('manifest')
466 def manifest(web, req, tmpl):
467 def manifest(web, req, tmpl):
467 """
468 """
468 /manifest[/{revision}[/{path}]]
469 /manifest[/{revision}[/{path}]]
469 -------------------------------
470 -------------------------------
470
471
471 Show information about a directory.
472 Show information about a directory.
472
473
473 If the URL path arguments are omitted, information about the root
474 If the URL path arguments are omitted, information about the root
474 directory for the ``tip`` changeset will be shown.
475 directory for the ``tip`` changeset will be shown.
475
476
476 Because this handler can only show information for directories, it
477 Because this handler can only show information for directories, it
477 is recommended to use the ``file`` handler instead, as it can handle both
478 is recommended to use the ``file`` handler instead, as it can handle both
478 directories and files.
479 directories and files.
479
480
480 The ``manifest`` template will be rendered for this handler.
481 The ``manifest`` template will be rendered for this handler.
481 """
482 """
482 if 'node' in req.form:
483 if 'node' in req.form:
483 ctx = webutil.changectx(web.repo, req)
484 ctx = webutil.changectx(web.repo, req)
484 symrev = webutil.symrevorshortnode(req, ctx)
485 symrev = webutil.symrevorshortnode(req, ctx)
485 else:
486 else:
486 ctx = web.repo['tip']
487 ctx = web.repo['tip']
487 symrev = 'tip'
488 symrev = 'tip'
488 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
489 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
489 mf = ctx.manifest()
490 mf = ctx.manifest()
490 node = ctx.node()
491 node = ctx.node()
491
492
492 files = {}
493 files = {}
493 dirs = {}
494 dirs = {}
494 parity = paritygen(web.stripecount)
495 parity = paritygen(web.stripecount)
495
496
496 if path and path[-1] != "/":
497 if path and path[-1] != "/":
497 path += "/"
498 path += "/"
498 l = len(path)
499 l = len(path)
499 abspath = "/" + path
500 abspath = "/" + path
500
501
501 for full, n in mf.iteritems():
502 for full, n in mf.iteritems():
502 # the virtual path (working copy path) used for the full
503 # the virtual path (working copy path) used for the full
503 # (repository) path
504 # (repository) path
504 f = decodepath(full)
505 f = decodepath(full)
505
506
506 if f[:l] != path:
507 if f[:l] != path:
507 continue
508 continue
508 remain = f[l:]
509 remain = f[l:]
509 elements = remain.split('/')
510 elements = remain.split('/')
510 if len(elements) == 1:
511 if len(elements) == 1:
511 files[remain] = full
512 files[remain] = full
512 else:
513 else:
513 h = dirs # need to retain ref to dirs (root)
514 h = dirs # need to retain ref to dirs (root)
514 for elem in elements[0:-1]:
515 for elem in elements[0:-1]:
515 if elem not in h:
516 if elem not in h:
516 h[elem] = {}
517 h[elem] = {}
517 h = h[elem]
518 h = h[elem]
518 if len(h) > 1:
519 if len(h) > 1:
519 break
520 break
520 h[None] = None # denotes files present
521 h[None] = None # denotes files present
521
522
522 if mf and not files and not dirs:
523 if mf and not files and not dirs:
523 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
524 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
524
525
525 def filelist(**map):
526 def filelist(**map):
526 for f in sorted(files):
527 for f in sorted(files):
527 full = files[f]
528 full = files[f]
528
529
529 fctx = ctx.filectx(full)
530 fctx = ctx.filectx(full)
530 yield {"file": full,
531 yield {"file": full,
531 "parity": next(parity),
532 "parity": next(parity),
532 "basename": f,
533 "basename": f,
533 "date": fctx.date(),
534 "date": fctx.date(),
534 "size": fctx.size(),
535 "size": fctx.size(),
535 "permissions": mf.flags(full)}
536 "permissions": mf.flags(full)}
536
537
537 def dirlist(**map):
538 def dirlist(**map):
538 for d in sorted(dirs):
539 for d in sorted(dirs):
539
540
540 emptydirs = []
541 emptydirs = []
541 h = dirs[d]
542 h = dirs[d]
542 while isinstance(h, dict) and len(h) == 1:
543 while isinstance(h, dict) and len(h) == 1:
543 k, v = h.items()[0]
544 k, v = h.items()[0]
544 if v:
545 if v:
545 emptydirs.append(k)
546 emptydirs.append(k)
546 h = v
547 h = v
547
548
548 path = "%s%s" % (abspath, d)
549 path = "%s%s" % (abspath, d)
549 yield {"parity": next(parity),
550 yield {"parity": next(parity),
550 "path": path,
551 "path": path,
551 "emptydirs": "/".join(emptydirs),
552 "emptydirs": "/".join(emptydirs),
552 "basename": d}
553 "basename": d}
553
554
554 return tmpl("manifest",
555 return tmpl("manifest",
555 symrev=symrev,
556 symrev=symrev,
556 path=abspath,
557 path=abspath,
557 up=webutil.up(abspath),
558 up=webutil.up(abspath),
558 upparity=next(parity),
559 upparity=next(parity),
559 fentries=filelist,
560 fentries=filelist,
560 dentries=dirlist,
561 dentries=dirlist,
561 archives=web.archivelist(hex(node)),
562 archives=web.archivelist(hex(node)),
562 **webutil.commonentry(web.repo, ctx))
563 **webutil.commonentry(web.repo, ctx))
563
564
564 @webcommand('tags')
565 @webcommand('tags')
565 def tags(web, req, tmpl):
566 def tags(web, req, tmpl):
566 """
567 """
567 /tags
568 /tags
568 -----
569 -----
569
570
570 Show information about tags.
571 Show information about tags.
571
572
572 No arguments are accepted.
573 No arguments are accepted.
573
574
574 The ``tags`` template is rendered.
575 The ``tags`` template is rendered.
575 """
576 """
576 i = list(reversed(web.repo.tagslist()))
577 i = list(reversed(web.repo.tagslist()))
577 parity = paritygen(web.stripecount)
578 parity = paritygen(web.stripecount)
578
579
579 def entries(notip, latestonly, **map):
580 def entries(notip, latestonly, **map):
580 t = i
581 t = i
581 if notip:
582 if notip:
582 t = [(k, n) for k, n in i if k != "tip"]
583 t = [(k, n) for k, n in i if k != "tip"]
583 if latestonly:
584 if latestonly:
584 t = t[:1]
585 t = t[:1]
585 for k, n in t:
586 for k, n in t:
586 yield {"parity": next(parity),
587 yield {"parity": next(parity),
587 "tag": k,
588 "tag": k,
588 "date": web.repo[n].date(),
589 "date": web.repo[n].date(),
589 "node": hex(n)}
590 "node": hex(n)}
590
591
591 return tmpl("tags",
592 return tmpl("tags",
592 node=hex(web.repo.changelog.tip()),
593 node=hex(web.repo.changelog.tip()),
593 entries=lambda **x: entries(False, False, **x),
594 entries=lambda **x: entries(False, False, **x),
594 entriesnotip=lambda **x: entries(True, False, **x),
595 entriesnotip=lambda **x: entries(True, False, **x),
595 latestentry=lambda **x: entries(True, True, **x))
596 latestentry=lambda **x: entries(True, True, **x))
596
597
597 @webcommand('bookmarks')
598 @webcommand('bookmarks')
598 def bookmarks(web, req, tmpl):
599 def bookmarks(web, req, tmpl):
599 """
600 """
600 /bookmarks
601 /bookmarks
601 ----------
602 ----------
602
603
603 Show information about bookmarks.
604 Show information about bookmarks.
604
605
605 No arguments are accepted.
606 No arguments are accepted.
606
607
607 The ``bookmarks`` template is rendered.
608 The ``bookmarks`` template is rendered.
608 """
609 """
609 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
610 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
610 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
611 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
611 i = sorted(i, key=sortkey, reverse=True)
612 i = sorted(i, key=sortkey, reverse=True)
612 parity = paritygen(web.stripecount)
613 parity = paritygen(web.stripecount)
613
614
614 def entries(latestonly, **map):
615 def entries(latestonly, **map):
615 t = i
616 t = i
616 if latestonly:
617 if latestonly:
617 t = i[:1]
618 t = i[:1]
618 for k, n in t:
619 for k, n in t:
619 yield {"parity": next(parity),
620 yield {"parity": next(parity),
620 "bookmark": k,
621 "bookmark": k,
621 "date": web.repo[n].date(),
622 "date": web.repo[n].date(),
622 "node": hex(n)}
623 "node": hex(n)}
623
624
624 if i:
625 if i:
625 latestrev = i[0][1]
626 latestrev = i[0][1]
626 else:
627 else:
627 latestrev = -1
628 latestrev = -1
628
629
629 return tmpl("bookmarks",
630 return tmpl("bookmarks",
630 node=hex(web.repo.changelog.tip()),
631 node=hex(web.repo.changelog.tip()),
631 lastchange=[{"date": web.repo[latestrev].date()}],
632 lastchange=[{"date": web.repo[latestrev].date()}],
632 entries=lambda **x: entries(latestonly=False, **x),
633 entries=lambda **x: entries(latestonly=False, **x),
633 latestentry=lambda **x: entries(latestonly=True, **x))
634 latestentry=lambda **x: entries(latestonly=True, **x))
634
635
635 @webcommand('branches')
636 @webcommand('branches')
636 def branches(web, req, tmpl):
637 def branches(web, req, tmpl):
637 """
638 """
638 /branches
639 /branches
639 ---------
640 ---------
640
641
641 Show information about branches.
642 Show information about branches.
642
643
643 All known branches are contained in the output, even closed branches.
644 All known branches are contained in the output, even closed branches.
644
645
645 No arguments are accepted.
646 No arguments are accepted.
646
647
647 The ``branches`` template is rendered.
648 The ``branches`` template is rendered.
648 """
649 """
649 entries = webutil.branchentries(web.repo, web.stripecount)
650 entries = webutil.branchentries(web.repo, web.stripecount)
650 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
651 latestentry = webutil.branchentries(web.repo, web.stripecount, 1)
651 return tmpl('branches', node=hex(web.repo.changelog.tip()),
652 return tmpl('branches', node=hex(web.repo.changelog.tip()),
652 entries=entries, latestentry=latestentry)
653 entries=entries, latestentry=latestentry)
653
654
654 @webcommand('summary')
655 @webcommand('summary')
655 def summary(web, req, tmpl):
656 def summary(web, req, tmpl):
656 """
657 """
657 /summary
658 /summary
658 --------
659 --------
659
660
660 Show a summary of repository state.
661 Show a summary of repository state.
661
662
662 Information about the latest changesets, bookmarks, tags, and branches
663 Information about the latest changesets, bookmarks, tags, and branches
663 is captured by this handler.
664 is captured by this handler.
664
665
665 The ``summary`` template is rendered.
666 The ``summary`` template is rendered.
666 """
667 """
667 i = reversed(web.repo.tagslist())
668 i = reversed(web.repo.tagslist())
668
669
669 def tagentries(**map):
670 def tagentries(**map):
670 parity = paritygen(web.stripecount)
671 parity = paritygen(web.stripecount)
671 count = 0
672 count = 0
672 for k, n in i:
673 for k, n in i:
673 if k == "tip": # skip tip
674 if k == "tip": # skip tip
674 continue
675 continue
675
676
676 count += 1
677 count += 1
677 if count > 10: # limit to 10 tags
678 if count > 10: # limit to 10 tags
678 break
679 break
679
680
680 yield tmpl("tagentry",
681 yield tmpl("tagentry",
681 parity=next(parity),
682 parity=next(parity),
682 tag=k,
683 tag=k,
683 node=hex(n),
684 node=hex(n),
684 date=web.repo[n].date())
685 date=web.repo[n].date())
685
686
686 def bookmarks(**map):
687 def bookmarks(**map):
687 parity = paritygen(web.stripecount)
688 parity = paritygen(web.stripecount)
688 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
689 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
689 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
690 sortkey = lambda b: (web.repo[b[1]].rev(), b[0])
690 marks = sorted(marks, key=sortkey, reverse=True)
691 marks = sorted(marks, key=sortkey, reverse=True)
691 for k, n in marks[:10]: # limit to 10 bookmarks
692 for k, n in marks[:10]: # limit to 10 bookmarks
692 yield {'parity': next(parity),
693 yield {'parity': next(parity),
693 'bookmark': k,
694 'bookmark': k,
694 'date': web.repo[n].date(),
695 'date': web.repo[n].date(),
695 'node': hex(n)}
696 'node': hex(n)}
696
697
697 def changelist(**map):
698 def changelist(**map):
698 parity = paritygen(web.stripecount, offset=start - end)
699 parity = paritygen(web.stripecount, offset=start - end)
699 l = [] # build a list in forward order for efficiency
700 l = [] # build a list in forward order for efficiency
700 revs = []
701 revs = []
701 if start < end:
702 if start < end:
702 revs = web.repo.changelog.revs(start, end - 1)
703 revs = web.repo.changelog.revs(start, end - 1)
703 for i in revs:
704 for i in revs:
704 ctx = web.repo[i]
705 ctx = web.repo[i]
705
706
706 l.append(tmpl(
707 l.append(tmpl(
707 'shortlogentry',
708 'shortlogentry',
708 parity=next(parity),
709 parity=next(parity),
709 **webutil.commonentry(web.repo, ctx)))
710 **webutil.commonentry(web.repo, ctx)))
710
711
711 for entry in reversed(l):
712 for entry in reversed(l):
712 yield entry
713 yield entry
713
714
714 tip = web.repo['tip']
715 tip = web.repo['tip']
715 count = len(web.repo)
716 count = len(web.repo)
716 start = max(0, count - web.maxchanges)
717 start = max(0, count - web.maxchanges)
717 end = min(count, start + web.maxchanges)
718 end = min(count, start + web.maxchanges)
718
719
719 return tmpl("summary",
720 return tmpl("summary",
720 desc=web.config("web", "description", "unknown"),
721 desc=web.config("web", "description", "unknown"),
721 owner=get_contact(web.config) or "unknown",
722 owner=get_contact(web.config) or "unknown",
722 lastchange=tip.date(),
723 lastchange=tip.date(),
723 tags=tagentries,
724 tags=tagentries,
724 bookmarks=bookmarks,
725 bookmarks=bookmarks,
725 branches=webutil.branchentries(web.repo, web.stripecount, 10),
726 branches=webutil.branchentries(web.repo, web.stripecount, 10),
726 shortlog=changelist,
727 shortlog=changelist,
727 node=tip.hex(),
728 node=tip.hex(),
728 symrev='tip',
729 symrev='tip',
729 archives=web.archivelist("tip"),
730 archives=web.archivelist("tip"),
730 labels=web.configlist('web', 'labels'))
731 labels=web.configlist('web', 'labels'))
731
732
732 @webcommand('filediff')
733 @webcommand('filediff')
733 def filediff(web, req, tmpl):
734 def filediff(web, req, tmpl):
734 """
735 """
735 /diff/{revision}/{path}
736 /diff/{revision}/{path}
736 -----------------------
737 -----------------------
737
738
738 Show how a file changed in a particular commit.
739 Show how a file changed in a particular commit.
739
740
740 The ``filediff`` template is rendered.
741 The ``filediff`` template is rendered.
741
742
742 This handler is registered under both the ``/diff`` and ``/filediff``
743 This handler is registered under both the ``/diff`` and ``/filediff``
743 paths. ``/diff`` is used in modern code.
744 paths. ``/diff`` is used in modern code.
744 """
745 """
745 fctx, ctx = None, None
746 fctx, ctx = None, None
746 try:
747 try:
747 fctx = webutil.filectx(web.repo, req)
748 fctx = webutil.filectx(web.repo, req)
748 except LookupError:
749 except LookupError:
749 ctx = webutil.changectx(web.repo, req)
750 ctx = webutil.changectx(web.repo, req)
750 path = webutil.cleanpath(web.repo, req.form['file'][0])
751 path = webutil.cleanpath(web.repo, req.form['file'][0])
751 if path not in ctx.files():
752 if path not in ctx.files():
752 raise
753 raise
753
754
754 if fctx is not None:
755 if fctx is not None:
755 path = fctx.path()
756 path = fctx.path()
756 ctx = fctx.changectx()
757 ctx = fctx.changectx()
757 basectx = ctx.p1()
758 basectx = ctx.p1()
758
759
759 style = web.config('web', 'style', 'paper')
760 style = web.config('web', 'style', 'paper')
760 if 'style' in req.form:
761 if 'style' in req.form:
761 style = req.form['style'][0]
762 style = req.form['style'][0]
762
763
763 diffs = webutil.diffs(web, tmpl, ctx, basectx, [path], style)
764 diffs = webutil.diffs(web, tmpl, ctx, basectx, [path], style)
764 if fctx is not None:
765 if fctx is not None:
765 rename = webutil.renamelink(fctx)
766 rename = webutil.renamelink(fctx)
766 ctx = fctx
767 ctx = fctx
767 else:
768 else:
768 rename = []
769 rename = []
769 ctx = ctx
770 ctx = ctx
770 return tmpl("filediff",
771 return tmpl("filediff",
771 file=path,
772 file=path,
772 symrev=webutil.symrevorshortnode(req, ctx),
773 symrev=webutil.symrevorshortnode(req, ctx),
773 rename=rename,
774 rename=rename,
774 diff=diffs,
775 diff=diffs,
775 **webutil.commonentry(web.repo, ctx))
776 **webutil.commonentry(web.repo, ctx))
776
777
777 diff = webcommand('diff')(filediff)
778 diff = webcommand('diff')(filediff)
778
779
779 @webcommand('comparison')
780 @webcommand('comparison')
780 def comparison(web, req, tmpl):
781 def comparison(web, req, tmpl):
781 """
782 """
782 /comparison/{revision}/{path}
783 /comparison/{revision}/{path}
783 -----------------------------
784 -----------------------------
784
785
785 Show a comparison between the old and new versions of a file from changes
786 Show a comparison between the old and new versions of a file from changes
786 made on a particular revision.
787 made on a particular revision.
787
788
788 This is similar to the ``diff`` handler. However, this form features
789 This is similar to the ``diff`` handler. However, this form features
789 a split or side-by-side diff rather than a unified diff.
790 a split or side-by-side diff rather than a unified diff.
790
791
791 The ``context`` query string argument can be used to control the lines of
792 The ``context`` query string argument can be used to control the lines of
792 context in the diff.
793 context in the diff.
793
794
794 The ``filecomparison`` template is rendered.
795 The ``filecomparison`` template is rendered.
795 """
796 """
796 ctx = webutil.changectx(web.repo, req)
797 ctx = webutil.changectx(web.repo, req)
797 if 'file' not in req.form:
798 if 'file' not in req.form:
798 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
799 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
799 path = webutil.cleanpath(web.repo, req.form['file'][0])
800 path = webutil.cleanpath(web.repo, req.form['file'][0])
800
801
801 parsecontext = lambda v: v == 'full' and -1 or int(v)
802 parsecontext = lambda v: v == 'full' and -1 or int(v)
802 if 'context' in req.form:
803 if 'context' in req.form:
803 context = parsecontext(req.form['context'][0])
804 context = parsecontext(req.form['context'][0])
804 else:
805 else:
805 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
806 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
806
807
807 def filelines(f):
808 def filelines(f):
808 if util.binary(f.data()):
809 if util.binary(f.data()):
809 mt = mimetypes.guess_type(f.path())[0]
810 mt = mimetypes.guess_type(f.path())[0]
810 if not mt:
811 if not mt:
811 mt = 'application/octet-stream'
812 mt = 'application/octet-stream'
812 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
813 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
813 return f.data().splitlines()
814 return f.data().splitlines()
814
815
815 fctx = None
816 fctx = None
816 parent = ctx.p1()
817 parent = ctx.p1()
817 leftrev = parent.rev()
818 leftrev = parent.rev()
818 leftnode = parent.node()
819 leftnode = parent.node()
819 rightrev = ctx.rev()
820 rightrev = ctx.rev()
820 rightnode = ctx.node()
821 rightnode = ctx.node()
821 if path in ctx:
822 if path in ctx:
822 fctx = ctx[path]
823 fctx = ctx[path]
823 rightlines = filelines(fctx)
824 rightlines = filelines(fctx)
824 if path not in parent:
825 if path not in parent:
825 leftlines = ()
826 leftlines = ()
826 else:
827 else:
827 pfctx = parent[path]
828 pfctx = parent[path]
828 leftlines = filelines(pfctx)
829 leftlines = filelines(pfctx)
829 else:
830 else:
830 rightlines = ()
831 rightlines = ()
831 pfctx = ctx.parents()[0][path]
832 pfctx = ctx.parents()[0][path]
832 leftlines = filelines(pfctx)
833 leftlines = filelines(pfctx)
833
834
834 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
835 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
835 if fctx is not None:
836 if fctx is not None:
836 rename = webutil.renamelink(fctx)
837 rename = webutil.renamelink(fctx)
837 ctx = fctx
838 ctx = fctx
838 else:
839 else:
839 rename = []
840 rename = []
840 ctx = ctx
841 ctx = ctx
841 return tmpl('filecomparison',
842 return tmpl('filecomparison',
842 file=path,
843 file=path,
843 symrev=webutil.symrevorshortnode(req, ctx),
844 symrev=webutil.symrevorshortnode(req, ctx),
844 rename=rename,
845 rename=rename,
845 leftrev=leftrev,
846 leftrev=leftrev,
846 leftnode=hex(leftnode),
847 leftnode=hex(leftnode),
847 rightrev=rightrev,
848 rightrev=rightrev,
848 rightnode=hex(rightnode),
849 rightnode=hex(rightnode),
849 comparison=comparison,
850 comparison=comparison,
850 **webutil.commonentry(web.repo, ctx))
851 **webutil.commonentry(web.repo, ctx))
851
852
852 @webcommand('annotate')
853 @webcommand('annotate')
853 def annotate(web, req, tmpl):
854 def annotate(web, req, tmpl):
854 """
855 """
855 /annotate/{revision}/{path}
856 /annotate/{revision}/{path}
856 ---------------------------
857 ---------------------------
857
858
858 Show changeset information for each line in a file.
859 Show changeset information for each line in a file.
859
860
860 The ``fileannotate`` template is rendered.
861 The ``fileannotate`` template is rendered.
861 """
862 """
862 fctx = webutil.filectx(web.repo, req)
863 fctx = webutil.filectx(web.repo, req)
863 f = fctx.path()
864 f = fctx.path()
864 parity = paritygen(web.stripecount)
865 parity = paritygen(web.stripecount)
865
866
866 # parents() is called once per line and several lines likely belong to
867 # parents() is called once per line and several lines likely belong to
867 # same revision. So it is worth caching.
868 # same revision. So it is worth caching.
868 # TODO there are still redundant operations within basefilectx.parents()
869 # TODO there are still redundant operations within basefilectx.parents()
869 # and from the fctx.annotate() call itself that could be cached.
870 # and from the fctx.annotate() call itself that could be cached.
870 parentscache = {}
871 parentscache = {}
871 def parents(f):
872 def parents(f):
872 rev = f.rev()
873 rev = f.rev()
873 if rev not in parentscache:
874 if rev not in parentscache:
874 parentscache[rev] = []
875 parentscache[rev] = []
875 for p in f.parents():
876 for p in f.parents():
876 entry = {
877 entry = {
877 'node': p.hex(),
878 'node': p.hex(),
878 'rev': p.rev(),
879 'rev': p.rev(),
879 }
880 }
880 parentscache[rev].append(entry)
881 parentscache[rev].append(entry)
881
882
882 for p in parentscache[rev]:
883 for p in parentscache[rev]:
883 yield p
884 yield p
884
885
885 def annotate(**map):
886 def annotate(**map):
886 if util.binary(fctx.data()):
887 if util.binary(fctx.data()):
887 mt = (mimetypes.guess_type(fctx.path())[0]
888 mt = (mimetypes.guess_type(fctx.path())[0]
888 or 'application/octet-stream')
889 or 'application/octet-stream')
889 lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
890 lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
890 else:
891 else:
891 lines = webutil.annotate(fctx, web.repo.ui)
892 lines = webutil.annotate(fctx, web.repo.ui)
892
893
893 previousrev = None
894 previousrev = None
894 blockparitygen = paritygen(1)
895 blockparitygen = paritygen(1)
895 for lineno, ((f, targetline), l) in enumerate(lines):
896 for lineno, ((f, targetline), l) in enumerate(lines):
896 rev = f.rev()
897 rev = f.rev()
897 if rev != previousrev:
898 if rev != previousrev:
898 blockhead = True
899 blockhead = True
899 blockparity = next(blockparitygen)
900 blockparity = next(blockparitygen)
900 else:
901 else:
901 blockhead = None
902 blockhead = None
902 previousrev = rev
903 previousrev = rev
903 yield {"parity": next(parity),
904 yield {"parity": next(parity),
904 "node": f.hex(),
905 "node": f.hex(),
905 "rev": rev,
906 "rev": rev,
906 "author": f.user(),
907 "author": f.user(),
907 "parents": parents(f),
908 "parents": parents(f),
908 "desc": f.description(),
909 "desc": f.description(),
909 "extra": f.extra(),
910 "extra": f.extra(),
910 "file": f.path(),
911 "file": f.path(),
911 "blockhead": blockhead,
912 "blockhead": blockhead,
912 "blockparity": blockparity,
913 "blockparity": blockparity,
913 "targetline": targetline,
914 "targetline": targetline,
914 "line": l,
915 "line": l,
915 "lineno": lineno + 1,
916 "lineno": lineno + 1,
916 "lineid": "l%d" % (lineno + 1),
917 "lineid": "l%d" % (lineno + 1),
917 "linenumber": "% 6d" % (lineno + 1),
918 "linenumber": "% 6d" % (lineno + 1),
918 "revdate": f.date()}
919 "revdate": f.date()}
919
920
920 return tmpl("fileannotate",
921 return tmpl("fileannotate",
921 file=f,
922 file=f,
922 annotate=annotate,
923 annotate=annotate,
923 path=webutil.up(f),
924 path=webutil.up(f),
924 symrev=webutil.symrevorshortnode(req, fctx),
925 symrev=webutil.symrevorshortnode(req, fctx),
925 rename=webutil.renamelink(fctx),
926 rename=webutil.renamelink(fctx),
926 permissions=fctx.manifest().flags(f),
927 permissions=fctx.manifest().flags(f),
927 **webutil.commonentry(web.repo, fctx))
928 **webutil.commonentry(web.repo, fctx))
928
929
929 @webcommand('filelog')
930 @webcommand('filelog')
930 def filelog(web, req, tmpl):
931 def filelog(web, req, tmpl):
931 """
932 """
932 /filelog/{revision}/{path}
933 /filelog/{revision}/{path}
933 --------------------------
934 --------------------------
934
935
935 Show information about the history of a file in the repository.
936 Show information about the history of a file in the repository.
936
937
937 The ``revcount`` query string argument can be defined to control the
938 The ``revcount`` query string argument can be defined to control the
938 maximum number of entries to show.
939 maximum number of entries to show.
939
940
940 The ``filelog`` template will be rendered.
941 The ``filelog`` template will be rendered.
941 """
942 """
942
943
943 try:
944 try:
944 fctx = webutil.filectx(web.repo, req)
945 fctx = webutil.filectx(web.repo, req)
945 f = fctx.path()
946 f = fctx.path()
946 fl = fctx.filelog()
947 fl = fctx.filelog()
947 except error.LookupError:
948 except error.LookupError:
948 f = webutil.cleanpath(web.repo, req.form['file'][0])
949 f = webutil.cleanpath(web.repo, req.form['file'][0])
949 fl = web.repo.file(f)
950 fl = web.repo.file(f)
950 numrevs = len(fl)
951 numrevs = len(fl)
951 if not numrevs: # file doesn't exist at all
952 if not numrevs: # file doesn't exist at all
952 raise
953 raise
953 rev = webutil.changectx(web.repo, req).rev()
954 rev = webutil.changectx(web.repo, req).rev()
954 first = fl.linkrev(0)
955 first = fl.linkrev(0)
955 if rev < first: # current rev is from before file existed
956 if rev < first: # current rev is from before file existed
956 raise
957 raise
957 frev = numrevs - 1
958 frev = numrevs - 1
958 while fl.linkrev(frev) > rev:
959 while fl.linkrev(frev) > rev:
959 frev -= 1
960 frev -= 1
960 fctx = web.repo.filectx(f, fl.linkrev(frev))
961 fctx = web.repo.filectx(f, fl.linkrev(frev))
961
962
962 revcount = web.maxshortchanges
963 revcount = web.maxshortchanges
963 if 'revcount' in req.form:
964 if 'revcount' in req.form:
964 try:
965 try:
965 revcount = int(req.form.get('revcount', [revcount])[0])
966 revcount = int(req.form.get('revcount', [revcount])[0])
966 revcount = max(revcount, 1)
967 revcount = max(revcount, 1)
967 tmpl.defaults['sessionvars']['revcount'] = revcount
968 tmpl.defaults['sessionvars']['revcount'] = revcount
968 except ValueError:
969 except ValueError:
969 pass
970 pass
970
971
972 lrange = webutil.linerange(req)
973
971 lessvars = copy.copy(tmpl.defaults['sessionvars'])
974 lessvars = copy.copy(tmpl.defaults['sessionvars'])
972 lessvars['revcount'] = max(revcount / 2, 1)
975 lessvars['revcount'] = max(revcount / 2, 1)
973 morevars = copy.copy(tmpl.defaults['sessionvars'])
976 morevars = copy.copy(tmpl.defaults['sessionvars'])
974 morevars['revcount'] = revcount * 2
977 morevars['revcount'] = revcount * 2
975
978
976 patch = 'patch' in req.form
979 patch = 'patch' in req.form
977 if patch:
980 if patch:
978 lessvars['patch'] = morevars['patch'] = req.form['patch'][0]
981 lessvars['patch'] = morevars['patch'] = req.form['patch'][0]
979
982
980 count = fctx.filerev() + 1
983 count = fctx.filerev() + 1
981 start = max(0, count - revcount) # first rev on this page
984 start = max(0, count - revcount) # first rev on this page
982 end = min(count, start + revcount) # last rev on this page
985 end = min(count, start + revcount) # last rev on this page
983 parity = paritygen(web.stripecount, offset=start - end)
986 parity = paritygen(web.stripecount, offset=start - end)
984
987
985 repo = web.repo
988 repo = web.repo
986 revs = fctx.filelog().revs(start, end - 1)
989 revs = fctx.filelog().revs(start, end - 1)
987 entries = []
990 entries = []
988
991
989 diffstyle = web.config('web', 'style', 'paper')
992 diffstyle = web.config('web', 'style', 'paper')
990 if 'style' in req.form:
993 if 'style' in req.form:
991 diffstyle = req.form['style'][0]
994 diffstyle = req.form['style'][0]
992
995
993 def diff(fctx):
996 def diff(fctx):
994 ctx = fctx.changectx()
997 ctx = fctx.changectx()
995 basectx = ctx.p1()
998 basectx = ctx.p1()
996 path = fctx.path()
999 path = fctx.path()
997 return webutil.diffs(web, tmpl, ctx, basectx, [path], diffstyle)
1000 return webutil.diffs(web, tmpl, ctx, basectx, [path], diffstyle)
998
1001
1002 linerange = None
1003 if lrange is not None:
1004 linerange = webutil.formatlinerange(*lrange)
1005 # deactivate numeric nav links when linerange is specified as this
1006 # would required a dedicated "revnav" class
1007 nav = None
1008 ancestors = context.blockancestors(fctx, *lrange)
1009 for i, (c, lr) in enumerate(ancestors, 1):
1010 diffs = None
1011 if patch:
1012 diffs = diff(c)
1013 # follow renames accross filtered (not in range) revisions
1014 path = c.path()
1015 entries.append(dict(
1016 parity=next(parity),
1017 filerev=c.rev(),
1018 file=path,
1019 diff=diffs,
1020 linerange=webutil.formatlinerange(*lr),
1021 **webutil.commonentry(repo, c)))
1022 if i == revcount:
1023 break
1024 lessvars['linerange'] = webutil.formatlinerange(*lrange)
1025 morevars['linerange'] = lessvars['linerange']
1026 else:
999 for i in revs:
1027 for i in revs:
1000 iterfctx = fctx.filectx(i)
1028 iterfctx = fctx.filectx(i)
1001 diffs = None
1029 diffs = None
1002 if patch:
1030 if patch:
1003 diffs = diff(iterfctx)
1031 diffs = diff(iterfctx)
1004 entries.append(dict(
1032 entries.append(dict(
1005 parity=next(parity),
1033 parity=next(parity),
1006 filerev=i,
1034 filerev=i,
1007 file=f,
1035 file=f,
1008 diff=diffs,
1036 diff=diffs,
1009 rename=webutil.renamelink(iterfctx),
1037 rename=webutil.renamelink(iterfctx),
1010 **webutil.commonentry(repo, iterfctx)))
1038 **webutil.commonentry(repo, iterfctx)))
1011 entries.reverse()
1039 entries.reverse()
1040 revnav = webutil.filerevnav(web.repo, fctx.path())
1041 nav = revnav.gen(end - 1, revcount, count)
1012
1042
1013 latestentry = entries[:1]
1043 latestentry = entries[:1]
1014
1044
1015 revnav = webutil.filerevnav(web.repo, fctx.path())
1016 nav = revnav.gen(end - 1, revcount, count)
1017 return tmpl("filelog",
1045 return tmpl("filelog",
1018 file=f,
1046 file=f,
1019 nav=nav,
1047 nav=nav,
1020 symrev=webutil.symrevorshortnode(req, fctx),
1048 symrev=webutil.symrevorshortnode(req, fctx),
1021 entries=entries,
1049 entries=entries,
1022 patch=patch,
1050 patch=patch,
1023 latestentry=latestentry,
1051 latestentry=latestentry,
1052 linerange=linerange,
1024 revcount=revcount,
1053 revcount=revcount,
1025 morevars=morevars,
1054 morevars=morevars,
1026 lessvars=lessvars,
1055 lessvars=lessvars,
1027 **webutil.commonentry(web.repo, fctx))
1056 **webutil.commonentry(web.repo, fctx))
1028
1057
1029 @webcommand('archive')
1058 @webcommand('archive')
1030 def archive(web, req, tmpl):
1059 def archive(web, req, tmpl):
1031 """
1060 """
1032 /archive/{revision}.{format}[/{path}]
1061 /archive/{revision}.{format}[/{path}]
1033 -------------------------------------
1062 -------------------------------------
1034
1063
1035 Obtain an archive of repository content.
1064 Obtain an archive of repository content.
1036
1065
1037 The content and type of the archive is defined by a URL path parameter.
1066 The content and type of the archive is defined by a URL path parameter.
1038 ``format`` is the file extension of the archive type to be generated. e.g.
1067 ``format`` is the file extension of the archive type to be generated. e.g.
1039 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1068 ``zip`` or ``tar.bz2``. Not all archive types may be allowed by your
1040 server configuration.
1069 server configuration.
1041
1070
1042 The optional ``path`` URL parameter controls content to include in the
1071 The optional ``path`` URL parameter controls content to include in the
1043 archive. If omitted, every file in the specified revision is present in the
1072 archive. If omitted, every file in the specified revision is present in the
1044 archive. If included, only the specified file or contents of the specified
1073 archive. If included, only the specified file or contents of the specified
1045 directory will be included in the archive.
1074 directory will be included in the archive.
1046
1075
1047 No template is used for this handler. Raw, binary content is generated.
1076 No template is used for this handler. Raw, binary content is generated.
1048 """
1077 """
1049
1078
1050 type_ = req.form.get('type', [None])[0]
1079 type_ = req.form.get('type', [None])[0]
1051 allowed = web.configlist("web", "allow_archive")
1080 allowed = web.configlist("web", "allow_archive")
1052 key = req.form['node'][0]
1081 key = req.form['node'][0]
1053
1082
1054 if type_ not in web.archivespecs:
1083 if type_ not in web.archivespecs:
1055 msg = 'Unsupported archive type: %s' % type_
1084 msg = 'Unsupported archive type: %s' % type_
1056 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1085 raise ErrorResponse(HTTP_NOT_FOUND, msg)
1057
1086
1058 if not ((type_ in allowed or
1087 if not ((type_ in allowed or
1059 web.configbool("web", "allow" + type_, False))):
1088 web.configbool("web", "allow" + type_, False))):
1060 msg = 'Archive type not allowed: %s' % type_
1089 msg = 'Archive type not allowed: %s' % type_
1061 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1090 raise ErrorResponse(HTTP_FORBIDDEN, msg)
1062
1091
1063 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1092 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
1064 cnode = web.repo.lookup(key)
1093 cnode = web.repo.lookup(key)
1065 arch_version = key
1094 arch_version = key
1066 if cnode == key or key == 'tip':
1095 if cnode == key or key == 'tip':
1067 arch_version = short(cnode)
1096 arch_version = short(cnode)
1068 name = "%s-%s" % (reponame, arch_version)
1097 name = "%s-%s" % (reponame, arch_version)
1069
1098
1070 ctx = webutil.changectx(web.repo, req)
1099 ctx = webutil.changectx(web.repo, req)
1071 pats = []
1100 pats = []
1072 matchfn = scmutil.match(ctx, [])
1101 matchfn = scmutil.match(ctx, [])
1073 file = req.form.get('file', None)
1102 file = req.form.get('file', None)
1074 if file:
1103 if file:
1075 pats = ['path:' + file[0]]
1104 pats = ['path:' + file[0]]
1076 matchfn = scmutil.match(ctx, pats, default='path')
1105 matchfn = scmutil.match(ctx, pats, default='path')
1077 if pats:
1106 if pats:
1078 files = [f for f in ctx.manifest().keys() if matchfn(f)]
1107 files = [f for f in ctx.manifest().keys() if matchfn(f)]
1079 if not files:
1108 if not files:
1080 raise ErrorResponse(HTTP_NOT_FOUND,
1109 raise ErrorResponse(HTTP_NOT_FOUND,
1081 'file(s) not found: %s' % file[0])
1110 'file(s) not found: %s' % file[0])
1082
1111
1083 mimetype, artype, extension, encoding = web.archivespecs[type_]
1112 mimetype, artype, extension, encoding = web.archivespecs[type_]
1084 headers = [
1113 headers = [
1085 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1114 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
1086 ]
1115 ]
1087 if encoding:
1116 if encoding:
1088 headers.append(('Content-Encoding', encoding))
1117 headers.append(('Content-Encoding', encoding))
1089 req.headers.extend(headers)
1118 req.headers.extend(headers)
1090 req.respond(HTTP_OK, mimetype)
1119 req.respond(HTTP_OK, mimetype)
1091
1120
1092 archival.archive(web.repo, req, cnode, artype, prefix=name,
1121 archival.archive(web.repo, req, cnode, artype, prefix=name,
1093 matchfn=matchfn,
1122 matchfn=matchfn,
1094 subrepos=web.configbool("web", "archivesubrepos"))
1123 subrepos=web.configbool("web", "archivesubrepos"))
1095 return []
1124 return []
1096
1125
1097
1126
1098 @webcommand('static')
1127 @webcommand('static')
1099 def static(web, req, tmpl):
1128 def static(web, req, tmpl):
1100 fname = req.form['file'][0]
1129 fname = req.form['file'][0]
1101 # a repo owner may set web.static in .hg/hgrc to get any file
1130 # a repo owner may set web.static in .hg/hgrc to get any file
1102 # readable by the user running the CGI script
1131 # readable by the user running the CGI script
1103 static = web.config("web", "static", None, untrusted=False)
1132 static = web.config("web", "static", None, untrusted=False)
1104 if not static:
1133 if not static:
1105 tp = web.templatepath or templater.templatepaths()
1134 tp = web.templatepath or templater.templatepaths()
1106 if isinstance(tp, str):
1135 if isinstance(tp, str):
1107 tp = [tp]
1136 tp = [tp]
1108 static = [os.path.join(p, 'static') for p in tp]
1137 static = [os.path.join(p, 'static') for p in tp]
1109 staticfile(static, fname, req)
1138 staticfile(static, fname, req)
1110 return []
1139 return []
1111
1140
1112 @webcommand('graph')
1141 @webcommand('graph')
1113 def graph(web, req, tmpl):
1142 def graph(web, req, tmpl):
1114 """
1143 """
1115 /graph[/{revision}]
1144 /graph[/{revision}]
1116 -------------------
1145 -------------------
1117
1146
1118 Show information about the graphical topology of the repository.
1147 Show information about the graphical topology of the repository.
1119
1148
1120 Information rendered by this handler can be used to create visual
1149 Information rendered by this handler can be used to create visual
1121 representations of repository topology.
1150 representations of repository topology.
1122
1151
1123 The ``revision`` URL parameter controls the starting changeset.
1152 The ``revision`` URL parameter controls the starting changeset.
1124
1153
1125 The ``revcount`` query string argument can define the number of changesets
1154 The ``revcount`` query string argument can define the number of changesets
1126 to show information for.
1155 to show information for.
1127
1156
1128 This handler will render the ``graph`` template.
1157 This handler will render the ``graph`` template.
1129 """
1158 """
1130
1159
1131 if 'node' in req.form:
1160 if 'node' in req.form:
1132 ctx = webutil.changectx(web.repo, req)
1161 ctx = webutil.changectx(web.repo, req)
1133 symrev = webutil.symrevorshortnode(req, ctx)
1162 symrev = webutil.symrevorshortnode(req, ctx)
1134 else:
1163 else:
1135 ctx = web.repo['tip']
1164 ctx = web.repo['tip']
1136 symrev = 'tip'
1165 symrev = 'tip'
1137 rev = ctx.rev()
1166 rev = ctx.rev()
1138
1167
1139 bg_height = 39
1168 bg_height = 39
1140 revcount = web.maxshortchanges
1169 revcount = web.maxshortchanges
1141 if 'revcount' in req.form:
1170 if 'revcount' in req.form:
1142 try:
1171 try:
1143 revcount = int(req.form.get('revcount', [revcount])[0])
1172 revcount = int(req.form.get('revcount', [revcount])[0])
1144 revcount = max(revcount, 1)
1173 revcount = max(revcount, 1)
1145 tmpl.defaults['sessionvars']['revcount'] = revcount
1174 tmpl.defaults['sessionvars']['revcount'] = revcount
1146 except ValueError:
1175 except ValueError:
1147 pass
1176 pass
1148
1177
1149 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1178 lessvars = copy.copy(tmpl.defaults['sessionvars'])
1150 lessvars['revcount'] = max(revcount / 2, 1)
1179 lessvars['revcount'] = max(revcount / 2, 1)
1151 morevars = copy.copy(tmpl.defaults['sessionvars'])
1180 morevars = copy.copy(tmpl.defaults['sessionvars'])
1152 morevars['revcount'] = revcount * 2
1181 morevars['revcount'] = revcount * 2
1153
1182
1154 count = len(web.repo)
1183 count = len(web.repo)
1155 pos = rev
1184 pos = rev
1156
1185
1157 uprev = min(max(0, count - 1), rev + revcount)
1186 uprev = min(max(0, count - 1), rev + revcount)
1158 downrev = max(0, rev - revcount)
1187 downrev = max(0, rev - revcount)
1159 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1188 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
1160
1189
1161 tree = []
1190 tree = []
1162 if pos != -1:
1191 if pos != -1:
1163 allrevs = web.repo.changelog.revs(pos, 0)
1192 allrevs = web.repo.changelog.revs(pos, 0)
1164 revs = []
1193 revs = []
1165 for i in allrevs:
1194 for i in allrevs:
1166 revs.append(i)
1195 revs.append(i)
1167 if len(revs) >= revcount:
1196 if len(revs) >= revcount:
1168 break
1197 break
1169
1198
1170 # We have to feed a baseset to dagwalker as it is expecting smartset
1199 # We have to feed a baseset to dagwalker as it is expecting smartset
1171 # object. This does not have a big impact on hgweb performance itself
1200 # object. This does not have a big impact on hgweb performance itself
1172 # since hgweb graphing code is not itself lazy yet.
1201 # since hgweb graphing code is not itself lazy yet.
1173 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1202 dag = graphmod.dagwalker(web.repo, smartset.baseset(revs))
1174 # As we said one line above... not lazy.
1203 # As we said one line above... not lazy.
1175 tree = list(graphmod.colored(dag, web.repo))
1204 tree = list(graphmod.colored(dag, web.repo))
1176
1205
1177 def getcolumns(tree):
1206 def getcolumns(tree):
1178 cols = 0
1207 cols = 0
1179 for (id, type, ctx, vtx, edges) in tree:
1208 for (id, type, ctx, vtx, edges) in tree:
1180 if type != graphmod.CHANGESET:
1209 if type != graphmod.CHANGESET:
1181 continue
1210 continue
1182 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1211 cols = max(cols, max([edge[0] for edge in edges] or [0]),
1183 max([edge[1] for edge in edges] or [0]))
1212 max([edge[1] for edge in edges] or [0]))
1184 return cols
1213 return cols
1185
1214
1186 def graphdata(usetuples, encodestr):
1215 def graphdata(usetuples, encodestr):
1187 data = []
1216 data = []
1188
1217
1189 row = 0
1218 row = 0
1190 for (id, type, ctx, vtx, edges) in tree:
1219 for (id, type, ctx, vtx, edges) in tree:
1191 if type != graphmod.CHANGESET:
1220 if type != graphmod.CHANGESET:
1192 continue
1221 continue
1193 node = str(ctx)
1222 node = str(ctx)
1194 age = encodestr(templatefilters.age(ctx.date()))
1223 age = encodestr(templatefilters.age(ctx.date()))
1195 desc = templatefilters.firstline(encodestr(ctx.description()))
1224 desc = templatefilters.firstline(encodestr(ctx.description()))
1196 desc = cgi.escape(templatefilters.nonempty(desc))
1225 desc = cgi.escape(templatefilters.nonempty(desc))
1197 user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
1226 user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
1198 branch = cgi.escape(encodestr(ctx.branch()))
1227 branch = cgi.escape(encodestr(ctx.branch()))
1199 try:
1228 try:
1200 branchnode = web.repo.branchtip(branch)
1229 branchnode = web.repo.branchtip(branch)
1201 except error.RepoLookupError:
1230 except error.RepoLookupError:
1202 branchnode = None
1231 branchnode = None
1203 branch = branch, branchnode == ctx.node()
1232 branch = branch, branchnode == ctx.node()
1204
1233
1205 if usetuples:
1234 if usetuples:
1206 data.append((node, vtx, edges, desc, user, age, branch,
1235 data.append((node, vtx, edges, desc, user, age, branch,
1207 [cgi.escape(encodestr(x)) for x in ctx.tags()],
1236 [cgi.escape(encodestr(x)) for x in ctx.tags()],
1208 [cgi.escape(encodestr(x))
1237 [cgi.escape(encodestr(x))
1209 for x in ctx.bookmarks()]))
1238 for x in ctx.bookmarks()]))
1210 else:
1239 else:
1211 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1240 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1212 'color': (edge[2] - 1) % 6 + 1,
1241 'color': (edge[2] - 1) % 6 + 1,
1213 'width': edge[3], 'bcolor': edge[4]}
1242 'width': edge[3], 'bcolor': edge[4]}
1214 for edge in edges]
1243 for edge in edges]
1215
1244
1216 data.append(
1245 data.append(
1217 {'node': node,
1246 {'node': node,
1218 'col': vtx[0],
1247 'col': vtx[0],
1219 'color': (vtx[1] - 1) % 6 + 1,
1248 'color': (vtx[1] - 1) % 6 + 1,
1220 'edges': edgedata,
1249 'edges': edgedata,
1221 'row': row,
1250 'row': row,
1222 'nextrow': row + 1,
1251 'nextrow': row + 1,
1223 'desc': desc,
1252 'desc': desc,
1224 'user': user,
1253 'user': user,
1225 'age': age,
1254 'age': age,
1226 'bookmarks': webutil.nodebookmarksdict(
1255 'bookmarks': webutil.nodebookmarksdict(
1227 web.repo, ctx.node()),
1256 web.repo, ctx.node()),
1228 'branches': webutil.nodebranchdict(web.repo, ctx),
1257 'branches': webutil.nodebranchdict(web.repo, ctx),
1229 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1258 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1230 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1259 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1231
1260
1232 row += 1
1261 row += 1
1233
1262
1234 return data
1263 return data
1235
1264
1236 cols = getcolumns(tree)
1265 cols = getcolumns(tree)
1237 rows = len(tree)
1266 rows = len(tree)
1238 canvasheight = (rows + 1) * bg_height - 27
1267 canvasheight = (rows + 1) * bg_height - 27
1239
1268
1240 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1269 return tmpl('graph', rev=rev, symrev=symrev, revcount=revcount,
1241 uprev=uprev,
1270 uprev=uprev,
1242 lessvars=lessvars, morevars=morevars, downrev=downrev,
1271 lessvars=lessvars, morevars=morevars, downrev=downrev,
1243 cols=cols, rows=rows,
1272 cols=cols, rows=rows,
1244 canvaswidth=(cols + 1) * bg_height,
1273 canvaswidth=(cols + 1) * bg_height,
1245 truecanvasheight=rows * bg_height,
1274 truecanvasheight=rows * bg_height,
1246 canvasheight=canvasheight, bg_height=bg_height,
1275 canvasheight=canvasheight, bg_height=bg_height,
1247 # {jsdata} will be passed to |json, so it must be in utf-8
1276 # {jsdata} will be passed to |json, so it must be in utf-8
1248 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
1277 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
1249 nodes=lambda **x: graphdata(False, str),
1278 nodes=lambda **x: graphdata(False, str),
1250 node=ctx.hex(), changenav=changenav)
1279 node=ctx.hex(), changenav=changenav)
1251
1280
1252 def _getdoc(e):
1281 def _getdoc(e):
1253 doc = e[0].__doc__
1282 doc = e[0].__doc__
1254 if doc:
1283 if doc:
1255 doc = _(doc).partition('\n')[0]
1284 doc = _(doc).partition('\n')[0]
1256 else:
1285 else:
1257 doc = _('(no help text available)')
1286 doc = _('(no help text available)')
1258 return doc
1287 return doc
1259
1288
1260 @webcommand('help')
1289 @webcommand('help')
1261 def help(web, req, tmpl):
1290 def help(web, req, tmpl):
1262 """
1291 """
1263 /help[/{topic}]
1292 /help[/{topic}]
1264 ---------------
1293 ---------------
1265
1294
1266 Render help documentation.
1295 Render help documentation.
1267
1296
1268 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1297 This web command is roughly equivalent to :hg:`help`. If a ``topic``
1269 is defined, that help topic will be rendered. If not, an index of
1298 is defined, that help topic will be rendered. If not, an index of
1270 available help topics will be rendered.
1299 available help topics will be rendered.
1271
1300
1272 The ``help`` template will be rendered when requesting help for a topic.
1301 The ``help`` template will be rendered when requesting help for a topic.
1273 ``helptopics`` will be rendered for the index of help topics.
1302 ``helptopics`` will be rendered for the index of help topics.
1274 """
1303 """
1275 from .. import commands, help as helpmod # avoid cycle
1304 from .. import commands, help as helpmod # avoid cycle
1276
1305
1277 topicname = req.form.get('node', [None])[0]
1306 topicname = req.form.get('node', [None])[0]
1278 if not topicname:
1307 if not topicname:
1279 def topics(**map):
1308 def topics(**map):
1280 for entries, summary, _doc in helpmod.helptable:
1309 for entries, summary, _doc in helpmod.helptable:
1281 yield {'topic': entries[0], 'summary': summary}
1310 yield {'topic': entries[0], 'summary': summary}
1282
1311
1283 early, other = [], []
1312 early, other = [], []
1284 primary = lambda s: s.partition('|')[0]
1313 primary = lambda s: s.partition('|')[0]
1285 for c, e in commands.table.iteritems():
1314 for c, e in commands.table.iteritems():
1286 doc = _getdoc(e)
1315 doc = _getdoc(e)
1287 if 'DEPRECATED' in doc or c.startswith('debug'):
1316 if 'DEPRECATED' in doc or c.startswith('debug'):
1288 continue
1317 continue
1289 cmd = primary(c)
1318 cmd = primary(c)
1290 if cmd.startswith('^'):
1319 if cmd.startswith('^'):
1291 early.append((cmd[1:], doc))
1320 early.append((cmd[1:], doc))
1292 else:
1321 else:
1293 other.append((cmd, doc))
1322 other.append((cmd, doc))
1294
1323
1295 early.sort()
1324 early.sort()
1296 other.sort()
1325 other.sort()
1297
1326
1298 def earlycommands(**map):
1327 def earlycommands(**map):
1299 for c, doc in early:
1328 for c, doc in early:
1300 yield {'topic': c, 'summary': doc}
1329 yield {'topic': c, 'summary': doc}
1301
1330
1302 def othercommands(**map):
1331 def othercommands(**map):
1303 for c, doc in other:
1332 for c, doc in other:
1304 yield {'topic': c, 'summary': doc}
1333 yield {'topic': c, 'summary': doc}
1305
1334
1306 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1335 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1307 othercommands=othercommands, title='Index')
1336 othercommands=othercommands, title='Index')
1308
1337
1309 # Render an index of sub-topics.
1338 # Render an index of sub-topics.
1310 if topicname in helpmod.subtopics:
1339 if topicname in helpmod.subtopics:
1311 topics = []
1340 topics = []
1312 for entries, summary, _doc in helpmod.subtopics[topicname]:
1341 for entries, summary, _doc in helpmod.subtopics[topicname]:
1313 topics.append({
1342 topics.append({
1314 'topic': '%s.%s' % (topicname, entries[0]),
1343 'topic': '%s.%s' % (topicname, entries[0]),
1315 'basename': entries[0],
1344 'basename': entries[0],
1316 'summary': summary,
1345 'summary': summary,
1317 })
1346 })
1318
1347
1319 return tmpl('helptopics', topics=topics, title=topicname,
1348 return tmpl('helptopics', topics=topics, title=topicname,
1320 subindex=True)
1349 subindex=True)
1321
1350
1322 u = webutil.wsgiui.load()
1351 u = webutil.wsgiui.load()
1323 u.verbose = True
1352 u.verbose = True
1324
1353
1325 # Render a page from a sub-topic.
1354 # Render a page from a sub-topic.
1326 if '.' in topicname:
1355 if '.' in topicname:
1327 # TODO implement support for rendering sections, like
1356 # TODO implement support for rendering sections, like
1328 # `hg help` works.
1357 # `hg help` works.
1329 topic, subtopic = topicname.split('.', 1)
1358 topic, subtopic = topicname.split('.', 1)
1330 if topic not in helpmod.subtopics:
1359 if topic not in helpmod.subtopics:
1331 raise ErrorResponse(HTTP_NOT_FOUND)
1360 raise ErrorResponse(HTTP_NOT_FOUND)
1332 else:
1361 else:
1333 topic = topicname
1362 topic = topicname
1334 subtopic = None
1363 subtopic = None
1335
1364
1336 try:
1365 try:
1337 doc = helpmod.help_(u, topic, subtopic=subtopic)
1366 doc = helpmod.help_(u, topic, subtopic=subtopic)
1338 except error.UnknownCommand:
1367 except error.UnknownCommand:
1339 raise ErrorResponse(HTTP_NOT_FOUND)
1368 raise ErrorResponse(HTTP_NOT_FOUND)
1340 return tmpl('help', topic=topicname, doc=doc)
1369 return tmpl('help', topic=topicname, doc=doc)
1341
1370
1342 # tell hggettext to extract docstrings from these functions:
1371 # tell hggettext to extract docstrings from these functions:
1343 i18nfunctions = commands.values()
1372 i18nfunctions = commands.values()
@@ -1,601 +1,622 b''
1 # hgweb/webutil.py - utility library for the web interface.
1 # hgweb/webutil.py - utility library for the web interface.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import copy
11 import copy
12 import difflib
12 import difflib
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, nullid, short
17 from ..node import hex, nullid, short
18
18
19 from .common import (
19 from .common import (
20 ErrorResponse,
20 ErrorResponse,
21 HTTP_BAD_REQUEST,
21 HTTP_NOT_FOUND,
22 HTTP_NOT_FOUND,
22 paritygen,
23 paritygen,
23 )
24 )
24
25
25 from .. import (
26 from .. import (
26 context,
27 context,
27 error,
28 error,
28 match,
29 match,
29 patch,
30 patch,
30 pathutil,
31 pathutil,
31 templatefilters,
32 templatefilters,
32 ui as uimod,
33 ui as uimod,
33 util,
34 util,
34 )
35 )
35
36
36 def up(p):
37 def up(p):
37 if p[0] != "/":
38 if p[0] != "/":
38 p = "/" + p
39 p = "/" + p
39 if p[-1] == "/":
40 if p[-1] == "/":
40 p = p[:-1]
41 p = p[:-1]
41 up = os.path.dirname(p)
42 up = os.path.dirname(p)
42 if up == "/":
43 if up == "/":
43 return "/"
44 return "/"
44 return up + "/"
45 return up + "/"
45
46
46 def _navseq(step, firststep=None):
47 def _navseq(step, firststep=None):
47 if firststep:
48 if firststep:
48 yield firststep
49 yield firststep
49 if firststep >= 20 and firststep <= 40:
50 if firststep >= 20 and firststep <= 40:
50 firststep = 50
51 firststep = 50
51 yield firststep
52 yield firststep
52 assert step > 0
53 assert step > 0
53 assert firststep > 0
54 assert firststep > 0
54 while step <= firststep:
55 while step <= firststep:
55 step *= 10
56 step *= 10
56 while True:
57 while True:
57 yield 1 * step
58 yield 1 * step
58 yield 3 * step
59 yield 3 * step
59 step *= 10
60 step *= 10
60
61
61 class revnav(object):
62 class revnav(object):
62
63
63 def __init__(self, repo):
64 def __init__(self, repo):
64 """Navigation generation object
65 """Navigation generation object
65
66
66 :repo: repo object we generate nav for
67 :repo: repo object we generate nav for
67 """
68 """
68 # used for hex generation
69 # used for hex generation
69 self._revlog = repo.changelog
70 self._revlog = repo.changelog
70
71
71 def __nonzero__(self):
72 def __nonzero__(self):
72 """return True if any revision to navigate over"""
73 """return True if any revision to navigate over"""
73 return self._first() is not None
74 return self._first() is not None
74
75
75 __bool__ = __nonzero__
76 __bool__ = __nonzero__
76
77
77 def _first(self):
78 def _first(self):
78 """return the minimum non-filtered changeset or None"""
79 """return the minimum non-filtered changeset or None"""
79 try:
80 try:
80 return next(iter(self._revlog))
81 return next(iter(self._revlog))
81 except StopIteration:
82 except StopIteration:
82 return None
83 return None
83
84
84 def hex(self, rev):
85 def hex(self, rev):
85 return hex(self._revlog.node(rev))
86 return hex(self._revlog.node(rev))
86
87
87 def gen(self, pos, pagelen, limit):
88 def gen(self, pos, pagelen, limit):
88 """computes label and revision id for navigation link
89 """computes label and revision id for navigation link
89
90
90 :pos: is the revision relative to which we generate navigation.
91 :pos: is the revision relative to which we generate navigation.
91 :pagelen: the size of each navigation page
92 :pagelen: the size of each navigation page
92 :limit: how far shall we link
93 :limit: how far shall we link
93
94
94 The return is:
95 The return is:
95 - a single element tuple
96 - a single element tuple
96 - containing a dictionary with a `before` and `after` key
97 - containing a dictionary with a `before` and `after` key
97 - values are generator functions taking arbitrary number of kwargs
98 - values are generator functions taking arbitrary number of kwargs
98 - yield items are dictionaries with `label` and `node` keys
99 - yield items are dictionaries with `label` and `node` keys
99 """
100 """
100 if not self:
101 if not self:
101 # empty repo
102 # empty repo
102 return ({'before': (), 'after': ()},)
103 return ({'before': (), 'after': ()},)
103
104
104 targets = []
105 targets = []
105 for f in _navseq(1, pagelen):
106 for f in _navseq(1, pagelen):
106 if f > limit:
107 if f > limit:
107 break
108 break
108 targets.append(pos + f)
109 targets.append(pos + f)
109 targets.append(pos - f)
110 targets.append(pos - f)
110 targets.sort()
111 targets.sort()
111
112
112 first = self._first()
113 first = self._first()
113 navbefore = [("(%i)" % first, self.hex(first))]
114 navbefore = [("(%i)" % first, self.hex(first))]
114 navafter = []
115 navafter = []
115 for rev in targets:
116 for rev in targets:
116 if rev not in self._revlog:
117 if rev not in self._revlog:
117 continue
118 continue
118 if pos < rev < limit:
119 if pos < rev < limit:
119 navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
120 navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
120 if 0 < rev < pos:
121 if 0 < rev < pos:
121 navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
122 navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
122
123
123
124
124 navafter.append(("tip", "tip"))
125 navafter.append(("tip", "tip"))
125
126
126 data = lambda i: {"label": i[0], "node": i[1]}
127 data = lambda i: {"label": i[0], "node": i[1]}
127 return ({'before': lambda **map: (data(i) for i in navbefore),
128 return ({'before': lambda **map: (data(i) for i in navbefore),
128 'after': lambda **map: (data(i) for i in navafter)},)
129 'after': lambda **map: (data(i) for i in navafter)},)
129
130
130 class filerevnav(revnav):
131 class filerevnav(revnav):
131
132
132 def __init__(self, repo, path):
133 def __init__(self, repo, path):
133 """Navigation generation object
134 """Navigation generation object
134
135
135 :repo: repo object we generate nav for
136 :repo: repo object we generate nav for
136 :path: path of the file we generate nav for
137 :path: path of the file we generate nav for
137 """
138 """
138 # used for iteration
139 # used for iteration
139 self._changelog = repo.unfiltered().changelog
140 self._changelog = repo.unfiltered().changelog
140 # used for hex generation
141 # used for hex generation
141 self._revlog = repo.file(path)
142 self._revlog = repo.file(path)
142
143
143 def hex(self, rev):
144 def hex(self, rev):
144 return hex(self._changelog.node(self._revlog.linkrev(rev)))
145 return hex(self._changelog.node(self._revlog.linkrev(rev)))
145
146
146 class _siblings(object):
147 class _siblings(object):
147 def __init__(self, siblings=None, hiderev=None):
148 def __init__(self, siblings=None, hiderev=None):
148 if siblings is None:
149 if siblings is None:
149 siblings = []
150 siblings = []
150 self.siblings = [s for s in siblings if s.node() != nullid]
151 self.siblings = [s for s in siblings if s.node() != nullid]
151 if len(self.siblings) == 1 and self.siblings[0].rev() == hiderev:
152 if len(self.siblings) == 1 and self.siblings[0].rev() == hiderev:
152 self.siblings = []
153 self.siblings = []
153
154
154 def __iter__(self):
155 def __iter__(self):
155 for s in self.siblings:
156 for s in self.siblings:
156 d = {
157 d = {
157 'node': s.hex(),
158 'node': s.hex(),
158 'rev': s.rev(),
159 'rev': s.rev(),
159 'user': s.user(),
160 'user': s.user(),
160 'date': s.date(),
161 'date': s.date(),
161 'description': s.description(),
162 'description': s.description(),
162 'branch': s.branch(),
163 'branch': s.branch(),
163 }
164 }
164 if util.safehasattr(s, 'path'):
165 if util.safehasattr(s, 'path'):
165 d['file'] = s.path()
166 d['file'] = s.path()
166 yield d
167 yield d
167
168
168 def __len__(self):
169 def __len__(self):
169 return len(self.siblings)
170 return len(self.siblings)
170
171
171 def annotate(fctx, ui):
172 def annotate(fctx, ui):
172 diffopts = patch.difffeatureopts(ui, untrusted=True,
173 diffopts = patch.difffeatureopts(ui, untrusted=True,
173 section='annotate', whitespace=True)
174 section='annotate', whitespace=True)
174 return fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)
175 return fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)
175
176
176 def parents(ctx, hide=None):
177 def parents(ctx, hide=None):
177 if isinstance(ctx, context.basefilectx):
178 if isinstance(ctx, context.basefilectx):
178 introrev = ctx.introrev()
179 introrev = ctx.introrev()
179 if ctx.changectx().rev() != introrev:
180 if ctx.changectx().rev() != introrev:
180 return _siblings([ctx.repo()[introrev]], hide)
181 return _siblings([ctx.repo()[introrev]], hide)
181 return _siblings(ctx.parents(), hide)
182 return _siblings(ctx.parents(), hide)
182
183
183 def children(ctx, hide=None):
184 def children(ctx, hide=None):
184 return _siblings(ctx.children(), hide)
185 return _siblings(ctx.children(), hide)
185
186
186 def renamelink(fctx):
187 def renamelink(fctx):
187 r = fctx.renamed()
188 r = fctx.renamed()
188 if r:
189 if r:
189 return [{'file': r[0], 'node': hex(r[1])}]
190 return [{'file': r[0], 'node': hex(r[1])}]
190 return []
191 return []
191
192
192 def nodetagsdict(repo, node):
193 def nodetagsdict(repo, node):
193 return [{"name": i} for i in repo.nodetags(node)]
194 return [{"name": i} for i in repo.nodetags(node)]
194
195
195 def nodebookmarksdict(repo, node):
196 def nodebookmarksdict(repo, node):
196 return [{"name": i} for i in repo.nodebookmarks(node)]
197 return [{"name": i} for i in repo.nodebookmarks(node)]
197
198
198 def nodebranchdict(repo, ctx):
199 def nodebranchdict(repo, ctx):
199 branches = []
200 branches = []
200 branch = ctx.branch()
201 branch = ctx.branch()
201 # If this is an empty repo, ctx.node() == nullid,
202 # If this is an empty repo, ctx.node() == nullid,
202 # ctx.branch() == 'default'.
203 # ctx.branch() == 'default'.
203 try:
204 try:
204 branchnode = repo.branchtip(branch)
205 branchnode = repo.branchtip(branch)
205 except error.RepoLookupError:
206 except error.RepoLookupError:
206 branchnode = None
207 branchnode = None
207 if branchnode == ctx.node():
208 if branchnode == ctx.node():
208 branches.append({"name": branch})
209 branches.append({"name": branch})
209 return branches
210 return branches
210
211
211 def nodeinbranch(repo, ctx):
212 def nodeinbranch(repo, ctx):
212 branches = []
213 branches = []
213 branch = ctx.branch()
214 branch = ctx.branch()
214 try:
215 try:
215 branchnode = repo.branchtip(branch)
216 branchnode = repo.branchtip(branch)
216 except error.RepoLookupError:
217 except error.RepoLookupError:
217 branchnode = None
218 branchnode = None
218 if branch != 'default' and branchnode != ctx.node():
219 if branch != 'default' and branchnode != ctx.node():
219 branches.append({"name": branch})
220 branches.append({"name": branch})
220 return branches
221 return branches
221
222
222 def nodebranchnodefault(ctx):
223 def nodebranchnodefault(ctx):
223 branches = []
224 branches = []
224 branch = ctx.branch()
225 branch = ctx.branch()
225 if branch != 'default':
226 if branch != 'default':
226 branches.append({"name": branch})
227 branches.append({"name": branch})
227 return branches
228 return branches
228
229
229 def showtag(repo, tmpl, t1, node=nullid, **args):
230 def showtag(repo, tmpl, t1, node=nullid, **args):
230 for t in repo.nodetags(node):
231 for t in repo.nodetags(node):
231 yield tmpl(t1, tag=t, **args)
232 yield tmpl(t1, tag=t, **args)
232
233
233 def showbookmark(repo, tmpl, t1, node=nullid, **args):
234 def showbookmark(repo, tmpl, t1, node=nullid, **args):
234 for t in repo.nodebookmarks(node):
235 for t in repo.nodebookmarks(node):
235 yield tmpl(t1, bookmark=t, **args)
236 yield tmpl(t1, bookmark=t, **args)
236
237
237 def branchentries(repo, stripecount, limit=0):
238 def branchentries(repo, stripecount, limit=0):
238 tips = []
239 tips = []
239 heads = repo.heads()
240 heads = repo.heads()
240 parity = paritygen(stripecount)
241 parity = paritygen(stripecount)
241 sortkey = lambda item: (not item[1], item[0].rev())
242 sortkey = lambda item: (not item[1], item[0].rev())
242
243
243 def entries(**map):
244 def entries(**map):
244 count = 0
245 count = 0
245 if not tips:
246 if not tips:
246 for tag, hs, tip, closed in repo.branchmap().iterbranches():
247 for tag, hs, tip, closed in repo.branchmap().iterbranches():
247 tips.append((repo[tip], closed))
248 tips.append((repo[tip], closed))
248 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
249 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
249 if limit > 0 and count >= limit:
250 if limit > 0 and count >= limit:
250 return
251 return
251 count += 1
252 count += 1
252 if closed:
253 if closed:
253 status = 'closed'
254 status = 'closed'
254 elif ctx.node() not in heads:
255 elif ctx.node() not in heads:
255 status = 'inactive'
256 status = 'inactive'
256 else:
257 else:
257 status = 'open'
258 status = 'open'
258 yield {
259 yield {
259 'parity': next(parity),
260 'parity': next(parity),
260 'branch': ctx.branch(),
261 'branch': ctx.branch(),
261 'status': status,
262 'status': status,
262 'node': ctx.hex(),
263 'node': ctx.hex(),
263 'date': ctx.date()
264 'date': ctx.date()
264 }
265 }
265
266
266 return entries
267 return entries
267
268
268 def cleanpath(repo, path):
269 def cleanpath(repo, path):
269 path = path.lstrip('/')
270 path = path.lstrip('/')
270 return pathutil.canonpath(repo.root, '', path)
271 return pathutil.canonpath(repo.root, '', path)
271
272
272 def changeidctx(repo, changeid):
273 def changeidctx(repo, changeid):
273 try:
274 try:
274 ctx = repo[changeid]
275 ctx = repo[changeid]
275 except error.RepoError:
276 except error.RepoError:
276 man = repo.manifestlog._revlog
277 man = repo.manifestlog._revlog
277 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
278 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
278
279
279 return ctx
280 return ctx
280
281
281 def changectx(repo, req):
282 def changectx(repo, req):
282 changeid = "tip"
283 changeid = "tip"
283 if 'node' in req.form:
284 if 'node' in req.form:
284 changeid = req.form['node'][0]
285 changeid = req.form['node'][0]
285 ipos = changeid.find(':')
286 ipos = changeid.find(':')
286 if ipos != -1:
287 if ipos != -1:
287 changeid = changeid[(ipos + 1):]
288 changeid = changeid[(ipos + 1):]
288 elif 'manifest' in req.form:
289 elif 'manifest' in req.form:
289 changeid = req.form['manifest'][0]
290 changeid = req.form['manifest'][0]
290
291
291 return changeidctx(repo, changeid)
292 return changeidctx(repo, changeid)
292
293
293 def basechangectx(repo, req):
294 def basechangectx(repo, req):
294 if 'node' in req.form:
295 if 'node' in req.form:
295 changeid = req.form['node'][0]
296 changeid = req.form['node'][0]
296 ipos = changeid.find(':')
297 ipos = changeid.find(':')
297 if ipos != -1:
298 if ipos != -1:
298 changeid = changeid[:ipos]
299 changeid = changeid[:ipos]
299 return changeidctx(repo, changeid)
300 return changeidctx(repo, changeid)
300
301
301 return None
302 return None
302
303
303 def filectx(repo, req):
304 def filectx(repo, req):
304 if 'file' not in req.form:
305 if 'file' not in req.form:
305 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
306 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
306 path = cleanpath(repo, req.form['file'][0])
307 path = cleanpath(repo, req.form['file'][0])
307 if 'node' in req.form:
308 if 'node' in req.form:
308 changeid = req.form['node'][0]
309 changeid = req.form['node'][0]
309 elif 'filenode' in req.form:
310 elif 'filenode' in req.form:
310 changeid = req.form['filenode'][0]
311 changeid = req.form['filenode'][0]
311 else:
312 else:
312 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
313 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
313 try:
314 try:
314 fctx = repo[changeid][path]
315 fctx = repo[changeid][path]
315 except error.RepoError:
316 except error.RepoError:
316 fctx = repo.filectx(path, fileid=changeid)
317 fctx = repo.filectx(path, fileid=changeid)
317
318
318 return fctx
319 return fctx
319
320
321 def linerange(req):
322 linerange = req.form.get('linerange')
323 if linerange is None:
324 return None
325 if len(linerange) > 1:
326 raise ErrorResponse(HTTP_BAD_REQUEST,
327 'redundant linerange parameter')
328 try:
329 fromline, toline = map(int, linerange[0].split(':', 1))
330 except ValueError:
331 raise ErrorResponse(HTTP_BAD_REQUEST,
332 'invalid linerange parameter')
333 try:
334 return util.processlinerange(fromline, toline)
335 except error.ParseError as exc:
336 raise ErrorResponse(HTTP_BAD_REQUEST, str(exc))
337
338 def formatlinerange(fromline, toline):
339 return '%d:%d' % (fromline + 1, toline)
340
320 def commonentry(repo, ctx):
341 def commonentry(repo, ctx):
321 node = ctx.node()
342 node = ctx.node()
322 return {
343 return {
323 'rev': ctx.rev(),
344 'rev': ctx.rev(),
324 'node': hex(node),
345 'node': hex(node),
325 'author': ctx.user(),
346 'author': ctx.user(),
326 'desc': ctx.description(),
347 'desc': ctx.description(),
327 'date': ctx.date(),
348 'date': ctx.date(),
328 'extra': ctx.extra(),
349 'extra': ctx.extra(),
329 'phase': ctx.phasestr(),
350 'phase': ctx.phasestr(),
330 'branch': nodebranchnodefault(ctx),
351 'branch': nodebranchnodefault(ctx),
331 'inbranch': nodeinbranch(repo, ctx),
352 'inbranch': nodeinbranch(repo, ctx),
332 'branches': nodebranchdict(repo, ctx),
353 'branches': nodebranchdict(repo, ctx),
333 'tags': nodetagsdict(repo, node),
354 'tags': nodetagsdict(repo, node),
334 'bookmarks': nodebookmarksdict(repo, node),
355 'bookmarks': nodebookmarksdict(repo, node),
335 'parent': lambda **x: parents(ctx),
356 'parent': lambda **x: parents(ctx),
336 'child': lambda **x: children(ctx),
357 'child': lambda **x: children(ctx),
337 }
358 }
338
359
339 def changelistentry(web, ctx, tmpl):
360 def changelistentry(web, ctx, tmpl):
340 '''Obtain a dictionary to be used for entries in a changelist.
361 '''Obtain a dictionary to be used for entries in a changelist.
341
362
342 This function is called when producing items for the "entries" list passed
363 This function is called when producing items for the "entries" list passed
343 to the "shortlog" and "changelog" templates.
364 to the "shortlog" and "changelog" templates.
344 '''
365 '''
345 repo = web.repo
366 repo = web.repo
346 rev = ctx.rev()
367 rev = ctx.rev()
347 n = ctx.node()
368 n = ctx.node()
348 showtags = showtag(repo, tmpl, 'changelogtag', n)
369 showtags = showtag(repo, tmpl, 'changelogtag', n)
349 files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
370 files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
350
371
351 entry = commonentry(repo, ctx)
372 entry = commonentry(repo, ctx)
352 entry.update(
373 entry.update(
353 allparents=lambda **x: parents(ctx),
374 allparents=lambda **x: parents(ctx),
354 parent=lambda **x: parents(ctx, rev - 1),
375 parent=lambda **x: parents(ctx, rev - 1),
355 child=lambda **x: children(ctx, rev + 1),
376 child=lambda **x: children(ctx, rev + 1),
356 changelogtag=showtags,
377 changelogtag=showtags,
357 files=files,
378 files=files,
358 )
379 )
359 return entry
380 return entry
360
381
361 def symrevorshortnode(req, ctx):
382 def symrevorshortnode(req, ctx):
362 if 'node' in req.form:
383 if 'node' in req.form:
363 return templatefilters.revescape(req.form['node'][0])
384 return templatefilters.revescape(req.form['node'][0])
364 else:
385 else:
365 return short(ctx.node())
386 return short(ctx.node())
366
387
367 def changesetentry(web, req, tmpl, ctx):
388 def changesetentry(web, req, tmpl, ctx):
368 '''Obtain a dictionary to be used to render the "changeset" template.'''
389 '''Obtain a dictionary to be used to render the "changeset" template.'''
369
390
370 showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
391 showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
371 showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
392 showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
372 ctx.node())
393 ctx.node())
373 showbranch = nodebranchnodefault(ctx)
394 showbranch = nodebranchnodefault(ctx)
374
395
375 files = []
396 files = []
376 parity = paritygen(web.stripecount)
397 parity = paritygen(web.stripecount)
377 for blockno, f in enumerate(ctx.files()):
398 for blockno, f in enumerate(ctx.files()):
378 template = f in ctx and 'filenodelink' or 'filenolink'
399 template = f in ctx and 'filenodelink' or 'filenolink'
379 files.append(tmpl(template,
400 files.append(tmpl(template,
380 node=ctx.hex(), file=f, blockno=blockno + 1,
401 node=ctx.hex(), file=f, blockno=blockno + 1,
381 parity=next(parity)))
402 parity=next(parity)))
382
403
383 basectx = basechangectx(web.repo, req)
404 basectx = basechangectx(web.repo, req)
384 if basectx is None:
405 if basectx is None:
385 basectx = ctx.p1()
406 basectx = ctx.p1()
386
407
387 style = web.config('web', 'style', 'paper')
408 style = web.config('web', 'style', 'paper')
388 if 'style' in req.form:
409 if 'style' in req.form:
389 style = req.form['style'][0]
410 style = req.form['style'][0]
390
411
391 diff = diffs(web, tmpl, ctx, basectx, None, style)
412 diff = diffs(web, tmpl, ctx, basectx, None, style)
392
413
393 parity = paritygen(web.stripecount)
414 parity = paritygen(web.stripecount)
394 diffstatsgen = diffstatgen(ctx, basectx)
415 diffstatsgen = diffstatgen(ctx, basectx)
395 diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
416 diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
396
417
397 return dict(
418 return dict(
398 diff=diff,
419 diff=diff,
399 symrev=symrevorshortnode(req, ctx),
420 symrev=symrevorshortnode(req, ctx),
400 basenode=basectx.hex(),
421 basenode=basectx.hex(),
401 changesettag=showtags,
422 changesettag=showtags,
402 changesetbookmark=showbookmarks,
423 changesetbookmark=showbookmarks,
403 changesetbranch=showbranch,
424 changesetbranch=showbranch,
404 files=files,
425 files=files,
405 diffsummary=lambda **x: diffsummary(diffstatsgen),
426 diffsummary=lambda **x: diffsummary(diffstatsgen),
406 diffstat=diffstats,
427 diffstat=diffstats,
407 archives=web.archivelist(ctx.hex()),
428 archives=web.archivelist(ctx.hex()),
408 **commonentry(web.repo, ctx))
429 **commonentry(web.repo, ctx))
409
430
410 def listfilediffs(tmpl, files, node, max):
431 def listfilediffs(tmpl, files, node, max):
411 for f in files[:max]:
432 for f in files[:max]:
412 yield tmpl('filedifflink', node=hex(node), file=f)
433 yield tmpl('filedifflink', node=hex(node), file=f)
413 if len(files) > max:
434 if len(files) > max:
414 yield tmpl('fileellipses')
435 yield tmpl('fileellipses')
415
436
416 def diffs(web, tmpl, ctx, basectx, files, style):
437 def diffs(web, tmpl, ctx, basectx, files, style):
417
438
418 def prettyprintlines(lines, blockno):
439 def prettyprintlines(lines, blockno):
419 for lineno, l in enumerate(lines, 1):
440 for lineno, l in enumerate(lines, 1):
420 difflineno = "%d.%d" % (blockno, lineno)
441 difflineno = "%d.%d" % (blockno, lineno)
421 if l.startswith('+'):
442 if l.startswith('+'):
422 ltype = "difflineplus"
443 ltype = "difflineplus"
423 elif l.startswith('-'):
444 elif l.startswith('-'):
424 ltype = "difflineminus"
445 ltype = "difflineminus"
425 elif l.startswith('@'):
446 elif l.startswith('@'):
426 ltype = "difflineat"
447 ltype = "difflineat"
427 else:
448 else:
428 ltype = "diffline"
449 ltype = "diffline"
429 yield tmpl(ltype,
450 yield tmpl(ltype,
430 line=l,
451 line=l,
431 lineno=lineno,
452 lineno=lineno,
432 lineid="l%s" % difflineno,
453 lineid="l%s" % difflineno,
433 linenumber="% 8s" % difflineno)
454 linenumber="% 8s" % difflineno)
434
455
435 repo = web.repo
456 repo = web.repo
436 if files:
457 if files:
437 m = match.exact(repo.root, repo.getcwd(), files)
458 m = match.exact(repo.root, repo.getcwd(), files)
438 else:
459 else:
439 m = match.always(repo.root, repo.getcwd())
460 m = match.always(repo.root, repo.getcwd())
440
461
441 diffopts = patch.diffopts(repo.ui, untrusted=True)
462 diffopts = patch.diffopts(repo.ui, untrusted=True)
442 node1 = basectx.node()
463 node1 = basectx.node()
443 node2 = ctx.node()
464 node2 = ctx.node()
444 parity = paritygen(web.stripecount)
465 parity = paritygen(web.stripecount)
445
466
446 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
467 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
447 for blockno, (header, hunks) in enumerate(diffhunks, 1):
468 for blockno, (header, hunks) in enumerate(diffhunks, 1):
448 if style != 'raw':
469 if style != 'raw':
449 header = header[1:]
470 header = header[1:]
450 lines = [h + '\n' for h in header]
471 lines = [h + '\n' for h in header]
451 for hunkrange, hunklines in hunks:
472 for hunkrange, hunklines in hunks:
452 lines.extend(hunklines)
473 lines.extend(hunklines)
453 if lines:
474 if lines:
454 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
475 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
455 lines=prettyprintlines(lines, blockno))
476 lines=prettyprintlines(lines, blockno))
456
477
457 def compare(tmpl, context, leftlines, rightlines):
478 def compare(tmpl, context, leftlines, rightlines):
458 '''Generator function that provides side-by-side comparison data.'''
479 '''Generator function that provides side-by-side comparison data.'''
459
480
460 def compline(type, leftlineno, leftline, rightlineno, rightline):
481 def compline(type, leftlineno, leftline, rightlineno, rightline):
461 lineid = leftlineno and ("l%s" % leftlineno) or ''
482 lineid = leftlineno and ("l%s" % leftlineno) or ''
462 lineid += rightlineno and ("r%s" % rightlineno) or ''
483 lineid += rightlineno and ("r%s" % rightlineno) or ''
463 return tmpl('comparisonline',
484 return tmpl('comparisonline',
464 type=type,
485 type=type,
465 lineid=lineid,
486 lineid=lineid,
466 leftlineno=leftlineno,
487 leftlineno=leftlineno,
467 leftlinenumber="% 6s" % (leftlineno or ''),
488 leftlinenumber="% 6s" % (leftlineno or ''),
468 leftline=leftline or '',
489 leftline=leftline or '',
469 rightlineno=rightlineno,
490 rightlineno=rightlineno,
470 rightlinenumber="% 6s" % (rightlineno or ''),
491 rightlinenumber="% 6s" % (rightlineno or ''),
471 rightline=rightline or '')
492 rightline=rightline or '')
472
493
473 def getblock(opcodes):
494 def getblock(opcodes):
474 for type, llo, lhi, rlo, rhi in opcodes:
495 for type, llo, lhi, rlo, rhi in opcodes:
475 len1 = lhi - llo
496 len1 = lhi - llo
476 len2 = rhi - rlo
497 len2 = rhi - rlo
477 count = min(len1, len2)
498 count = min(len1, len2)
478 for i in xrange(count):
499 for i in xrange(count):
479 yield compline(type=type,
500 yield compline(type=type,
480 leftlineno=llo + i + 1,
501 leftlineno=llo + i + 1,
481 leftline=leftlines[llo + i],
502 leftline=leftlines[llo + i],
482 rightlineno=rlo + i + 1,
503 rightlineno=rlo + i + 1,
483 rightline=rightlines[rlo + i])
504 rightline=rightlines[rlo + i])
484 if len1 > len2:
505 if len1 > len2:
485 for i in xrange(llo + count, lhi):
506 for i in xrange(llo + count, lhi):
486 yield compline(type=type,
507 yield compline(type=type,
487 leftlineno=i + 1,
508 leftlineno=i + 1,
488 leftline=leftlines[i],
509 leftline=leftlines[i],
489 rightlineno=None,
510 rightlineno=None,
490 rightline=None)
511 rightline=None)
491 elif len2 > len1:
512 elif len2 > len1:
492 for i in xrange(rlo + count, rhi):
513 for i in xrange(rlo + count, rhi):
493 yield compline(type=type,
514 yield compline(type=type,
494 leftlineno=None,
515 leftlineno=None,
495 leftline=None,
516 leftline=None,
496 rightlineno=i + 1,
517 rightlineno=i + 1,
497 rightline=rightlines[i])
518 rightline=rightlines[i])
498
519
499 s = difflib.SequenceMatcher(None, leftlines, rightlines)
520 s = difflib.SequenceMatcher(None, leftlines, rightlines)
500 if context < 0:
521 if context < 0:
501 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
522 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
502 else:
523 else:
503 for oc in s.get_grouped_opcodes(n=context):
524 for oc in s.get_grouped_opcodes(n=context):
504 yield tmpl('comparisonblock', lines=getblock(oc))
525 yield tmpl('comparisonblock', lines=getblock(oc))
505
526
506 def diffstatgen(ctx, basectx):
527 def diffstatgen(ctx, basectx):
507 '''Generator function that provides the diffstat data.'''
528 '''Generator function that provides the diffstat data.'''
508
529
509 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
530 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
510 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
531 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
511 while True:
532 while True:
512 yield stats, maxname, maxtotal, addtotal, removetotal, binary
533 yield stats, maxname, maxtotal, addtotal, removetotal, binary
513
534
514 def diffsummary(statgen):
535 def diffsummary(statgen):
515 '''Return a short summary of the diff.'''
536 '''Return a short summary of the diff.'''
516
537
517 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
538 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
518 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
539 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
519 len(stats), addtotal, removetotal)
540 len(stats), addtotal, removetotal)
520
541
521 def diffstat(tmpl, ctx, statgen, parity):
542 def diffstat(tmpl, ctx, statgen, parity):
522 '''Return a diffstat template for each file in the diff.'''
543 '''Return a diffstat template for each file in the diff.'''
523
544
524 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
545 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
525 files = ctx.files()
546 files = ctx.files()
526
547
527 def pct(i):
548 def pct(i):
528 if maxtotal == 0:
549 if maxtotal == 0:
529 return 0
550 return 0
530 return (float(i) / maxtotal) * 100
551 return (float(i) / maxtotal) * 100
531
552
532 fileno = 0
553 fileno = 0
533 for filename, adds, removes, isbinary in stats:
554 for filename, adds, removes, isbinary in stats:
534 template = filename in files and 'diffstatlink' or 'diffstatnolink'
555 template = filename in files and 'diffstatlink' or 'diffstatnolink'
535 total = adds + removes
556 total = adds + removes
536 fileno += 1
557 fileno += 1
537 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
558 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
538 total=total, addpct=pct(adds), removepct=pct(removes),
559 total=total, addpct=pct(adds), removepct=pct(removes),
539 parity=next(parity))
560 parity=next(parity))
540
561
541 class sessionvars(object):
562 class sessionvars(object):
542 def __init__(self, vars, start='?'):
563 def __init__(self, vars, start='?'):
543 self.start = start
564 self.start = start
544 self.vars = vars
565 self.vars = vars
545 def __getitem__(self, key):
566 def __getitem__(self, key):
546 return self.vars[key]
567 return self.vars[key]
547 def __setitem__(self, key, value):
568 def __setitem__(self, key, value):
548 self.vars[key] = value
569 self.vars[key] = value
549 def __copy__(self):
570 def __copy__(self):
550 return sessionvars(copy.copy(self.vars), self.start)
571 return sessionvars(copy.copy(self.vars), self.start)
551 def __iter__(self):
572 def __iter__(self):
552 separator = self.start
573 separator = self.start
553 for key, value in sorted(self.vars.iteritems()):
574 for key, value in sorted(self.vars.iteritems()):
554 yield {'name': key, 'value': str(value), 'separator': separator}
575 yield {'name': key, 'value': str(value), 'separator': separator}
555 separator = '&'
576 separator = '&'
556
577
557 class wsgiui(uimod.ui):
578 class wsgiui(uimod.ui):
558 # default termwidth breaks under mod_wsgi
579 # default termwidth breaks under mod_wsgi
559 def termwidth(self):
580 def termwidth(self):
560 return 80
581 return 80
561
582
562 def getwebsubs(repo):
583 def getwebsubs(repo):
563 websubtable = []
584 websubtable = []
564 websubdefs = repo.ui.configitems('websub')
585 websubdefs = repo.ui.configitems('websub')
565 # we must maintain interhg backwards compatibility
586 # we must maintain interhg backwards compatibility
566 websubdefs += repo.ui.configitems('interhg')
587 websubdefs += repo.ui.configitems('interhg')
567 for key, pattern in websubdefs:
588 for key, pattern in websubdefs:
568 # grab the delimiter from the character after the "s"
589 # grab the delimiter from the character after the "s"
569 unesc = pattern[1]
590 unesc = pattern[1]
570 delim = re.escape(unesc)
591 delim = re.escape(unesc)
571
592
572 # identify portions of the pattern, taking care to avoid escaped
593 # identify portions of the pattern, taking care to avoid escaped
573 # delimiters. the replace format and flags are optional, but
594 # delimiters. the replace format and flags are optional, but
574 # delimiters are required.
595 # delimiters are required.
575 match = re.match(
596 match = re.match(
576 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
597 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
577 % (delim, delim, delim), pattern)
598 % (delim, delim, delim), pattern)
578 if not match:
599 if not match:
579 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
600 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
580 % (key, pattern))
601 % (key, pattern))
581 continue
602 continue
582
603
583 # we need to unescape the delimiter for regexp and format
604 # we need to unescape the delimiter for regexp and format
584 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
605 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
585 regexp = delim_re.sub(unesc, match.group(1))
606 regexp = delim_re.sub(unesc, match.group(1))
586 format = delim_re.sub(unesc, match.group(2))
607 format = delim_re.sub(unesc, match.group(2))
587
608
588 # the pattern allows for 6 regexp flags, so set them if necessary
609 # the pattern allows for 6 regexp flags, so set them if necessary
589 flagin = match.group(3)
610 flagin = match.group(3)
590 flags = 0
611 flags = 0
591 if flagin:
612 if flagin:
592 for flag in flagin.upper():
613 for flag in flagin.upper():
593 flags |= re.__dict__[flag]
614 flags |= re.__dict__[flag]
594
615
595 try:
616 try:
596 regexp = re.compile(regexp, flags)
617 regexp = re.compile(regexp, flags)
597 websubtable.append((regexp, format))
618 websubtable.append((regexp, format))
598 except re.error:
619 except re.error:
599 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
620 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
600 % (key, regexp))
621 % (key, regexp))
601 return websubtable
622 return websubtable
@@ -1,85 +1,87 b''
1 {header}
1 {header}
2 <title>{repo|escape}: {file|escape} history</title>
2 <title>{repo|escape}: {file|escape} history</title>
3 <link rel="alternate" type="application/atom+xml"
3 <link rel="alternate" type="application/atom+xml"
4 href="{url|urlescape}atom-log/tip/{file|urlescape}" title="Atom feed for {repo|escape}:{file}" />
4 href="{url|urlescape}atom-log/tip/{file|urlescape}" title="Atom feed for {repo|escape}:{file}" />
5 <link rel="alternate" type="application/rss+xml"
5 <link rel="alternate" type="application/rss+xml"
6 href="{url|urlescape}rss-log/tip/{file|urlescape}" title="RSS feed for {repo|escape}:{file}" />
6 href="{url|urlescape}rss-log/tip/{file|urlescape}" title="RSS feed for {repo|escape}:{file}" />
7 </head>
7 </head>
8 <body>
8 <body>
9
9
10 <div class="container">
10 <div class="container">
11 <div class="menu">
11 <div class="menu">
12 <div class="logo">
12 <div class="logo">
13 <a href="{logourl}">
13 <a href="{logourl}">
14 <img src="{staticurl|urlescape}{logoimg}" alt="mercurial" /></a>
14 <img src="{staticurl|urlescape}{logoimg}" alt="mercurial" /></a>
15 </div>
15 </div>
16 <ul>
16 <ul>
17 <li><a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">log</a></li>
17 <li><a href="{url|urlescape}shortlog/{symrev}{sessionvars%urlparameter}">log</a></li>
18 <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
18 <li><a href="{url|urlescape}graph/{symrev}{sessionvars%urlparameter}">graph</a></li>
19 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
19 <li><a href="{url|urlescape}tags{sessionvars%urlparameter}">tags</a></li>
20 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
20 <li><a href="{url|urlescape}bookmarks{sessionvars%urlparameter}">bookmarks</a></li>
21 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
21 <li><a href="{url|urlescape}branches{sessionvars%urlparameter}">branches</a></li>
22 </ul>
22 </ul>
23 <ul>
23 <ul>
24 <li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
24 <li><a href="{url|urlescape}rev/{symrev}{sessionvars%urlparameter}">changeset</a></li>
25 <li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
25 <li><a href="{url|urlescape}file/{symrev}{path|urlescape}{sessionvars%urlparameter}">browse</a></li>
26 </ul>
26 </ul>
27 <ul>
27 <ul>
28 <li><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
28 <li><a href="{url|urlescape}file/{symrev}/{file|urlescape}{sessionvars%urlparameter}">file</a></li>
29 <li><a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
29 <li><a href="{url|urlescape}diff/{symrev}/{file|urlescape}{sessionvars%urlparameter}">diff</a></li>
30 <li><a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
30 <li><a href="{url|urlescape}comparison/{symrev}/{file|urlescape}{sessionvars%urlparameter}">comparison</a></li>
31 <li><a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
31 <li><a href="{url|urlescape}annotate/{symrev}/{file|urlescape}{sessionvars%urlparameter}">annotate</a></li>
32 <li class="active">file log</li>
32 <li class="active">file log</li>
33 <li><a href="{url|urlescape}raw-file/{symrev}/{file|urlescape}">raw</a></li>
33 <li><a href="{url|urlescape}raw-file/{symrev}/{file|urlescape}">raw</a></li>
34 </ul>
34 </ul>
35 <ul>
35 <ul>
36 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
36 <li><a href="{url|urlescape}help{sessionvars%urlparameter}">help</a></li>
37 </ul>
37 </ul>
38 <div class="atom-logo">
38 <div class="atom-logo">
39 <a href="{url|urlescape}atom-log/tip/{file|urlescape}" title="subscribe to atom feed">
39 <a href="{url|urlescape}atom-log/tip/{file|urlescape}" title="subscribe to atom feed">
40 <img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
40 <img class="atom-logo" src="{staticurl|urlescape}feed-icon-14x14.png" alt="atom feed" />
41 </a>
41 </a>
42 </div>
42 </div>
43 </div>
43 </div>
44
44
45 <div class="main">
45 <div class="main">
46 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
46 <h2 class="breadcrumb"><a href="/">Mercurial</a> {pathdef%breadcrumb}</h2>
47 <h3>
47 <h3>
48 log {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
48 log {file|escape} @ {rev}:<a href="{url|urlescape}rev/{node|short}{sessionvars%urlparameter}">{node|short}</a>
49 {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
49 {branch%changelogbranchname}{tags%changelogtag}{bookmarks%changelogtag}
50 {if(linerange,
51 ' (following lines {linerange} <a href="{url|urlescape}log/{symrev}/{file|urlescape}{sessionvars%urlparameter}">back to filelog</a>)')}
50 </h3>
52 </h3>
51
53
52 <form class="search" action="{url|urlescape}log">
54 <form class="search" action="{url|urlescape}log">
53 {sessionvars%hiddenformentry}
55 {sessionvars%hiddenformentry}
54 <p><input name="rev" id="search1" type="text" size="30" /></p>
56 <p><input name="rev" id="search1" type="text" size="30" /></p>
55 <div id="hint">{searchhint}</div>
57 <div id="hint">{searchhint}</div>
56 </form>
58 </form>
57
59
58 <div class="navigate">
60 <div class="navigate">
59 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{lessvars%urlparameter}">less</a>
61 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{lessvars%urlparameter}">less</a>
60 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{morevars%urlparameter}">more</a>
62 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{morevars%urlparameter}">more</a>
61 | {nav%filenav}</div>
63 | {nav%filenav}</div>
62
64
63 <table class="bigtable">
65 <table class="bigtable">
64 <thead>
66 <thead>
65 <tr>
67 <tr>
66 <th class="age">age</th>
68 <th class="age">age</th>
67 <th class="author">author</th>
69 <th class="author">author</th>
68 <th class="description">description</th>
70 <th class="description">description</th>
69 </tr>
71 </tr>
70 </thead>
72 </thead>
71 <tbody class="stripes2">
73 <tbody class="stripes2">
72 {entries%filelogentry}
74 {entries%filelogentry}
73 </tbody>
75 </tbody>
74 </table>
76 </table>
75
77
76 <div class="navigate">
78 <div class="navigate">
77 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{lessvars%urlparameter}">less</a>
79 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{lessvars%urlparameter}">less</a>
78 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{morevars%urlparameter}">more</a>
80 <a href="{url|urlescape}log/{symrev}/{file|urlescape}{morevars%urlparameter}">more</a>
79 | {nav%filenav}
81 | {nav%filenav}
80 </div>
82 </div>
81
83
82 </div>
84 </div>
83 </div>
85 </div>
84
86
85 {footer}
87 {footer}
@@ -1,1036 +1,1299 b''
1 #require serve
1 #require serve
2
2
3 $ hg init test
3 $ hg init test
4 $ cd test
4 $ cd test
5 $ echo b > b
5 $ echo b > b
6 $ hg ci -Am "b"
6 $ hg ci -Am "b"
7 adding b
7 adding b
8 $ echo a > a
8 $ echo a > a
9 $ hg ci -Am "first a"
9 $ hg ci -Am "first a"
10 adding a
10 adding a
11 $ hg tag -r 1 a-tag
11 $ hg tag -r 1 a-tag
12 $ hg bookmark -r 1 a-bookmark
12 $ hg bookmark -r 1 a-bookmark
13 $ hg rm a
13 $ hg rm a
14 $ hg ci -m "del a"
14 $ hg ci -m "del a"
15 $ hg branch a-branch
15 $ hg branch a-branch
16 marked working directory as branch a-branch
16 marked working directory as branch a-branch
17 (branches are permanent and global, did you want a bookmark?)
17 (branches are permanent and global, did you want a bookmark?)
18 $ echo b > a
18 $ echo b > a
19 $ hg ci -Am "second a"
19 $ hg ci -Am "second a"
20 adding a
20 adding a
21 $ hg rm a
21 $ hg rm a
22 $ hg ci -m "del2 a"
22 $ hg ci -m "del2 a"
23 $ hg mv b c
23 $ hg mv b c
24 $ hg ci -m "mv b"
24 $ hg ci -m "mv b"
25 $ echo c >> c
25 $ echo c >> c
26 $ hg ci -m "change c"
26 $ hg ci -m "change c"
27 $ hg log -p
27 $ hg log -p
28 changeset: 7:46c1a66bd8fc
28 changeset: 7:46c1a66bd8fc
29 branch: a-branch
29 branch: a-branch
30 tag: tip
30 tag: tip
31 user: test
31 user: test
32 date: Thu Jan 01 00:00:00 1970 +0000
32 date: Thu Jan 01 00:00:00 1970 +0000
33 summary: change c
33 summary: change c
34
34
35 diff -r c9637d3cc8ef -r 46c1a66bd8fc c
35 diff -r c9637d3cc8ef -r 46c1a66bd8fc c
36 --- a/c Thu Jan 01 00:00:00 1970 +0000
36 --- a/c Thu Jan 01 00:00:00 1970 +0000
37 +++ b/c Thu Jan 01 00:00:00 1970 +0000
37 +++ b/c Thu Jan 01 00:00:00 1970 +0000
38 @@ -1,1 +1,2 @@
38 @@ -1,1 +1,2 @@
39 b
39 b
40 +c
40 +c
41
41
42 changeset: 6:c9637d3cc8ef
42 changeset: 6:c9637d3cc8ef
43 branch: a-branch
43 branch: a-branch
44 user: test
44 user: test
45 date: Thu Jan 01 00:00:00 1970 +0000
45 date: Thu Jan 01 00:00:00 1970 +0000
46 summary: mv b
46 summary: mv b
47
47
48 diff -r 958bd88be4eb -r c9637d3cc8ef b
48 diff -r 958bd88be4eb -r c9637d3cc8ef b
49 --- a/b Thu Jan 01 00:00:00 1970 +0000
49 --- a/b Thu Jan 01 00:00:00 1970 +0000
50 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
50 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
51 @@ -1,1 +0,0 @@
51 @@ -1,1 +0,0 @@
52 -b
52 -b
53 diff -r 958bd88be4eb -r c9637d3cc8ef c
53 diff -r 958bd88be4eb -r c9637d3cc8ef c
54 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
54 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
55 +++ b/c Thu Jan 01 00:00:00 1970 +0000
55 +++ b/c Thu Jan 01 00:00:00 1970 +0000
56 @@ -0,0 +1,1 @@
56 @@ -0,0 +1,1 @@
57 +b
57 +b
58
58
59 changeset: 5:958bd88be4eb
59 changeset: 5:958bd88be4eb
60 branch: a-branch
60 branch: a-branch
61 user: test
61 user: test
62 date: Thu Jan 01 00:00:00 1970 +0000
62 date: Thu Jan 01 00:00:00 1970 +0000
63 summary: del2 a
63 summary: del2 a
64
64
65 diff -r 3f41bc784e7e -r 958bd88be4eb a
65 diff -r 3f41bc784e7e -r 958bd88be4eb a
66 --- a/a Thu Jan 01 00:00:00 1970 +0000
66 --- a/a Thu Jan 01 00:00:00 1970 +0000
67 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
67 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
68 @@ -1,1 +0,0 @@
68 @@ -1,1 +0,0 @@
69 -b
69 -b
70
70
71 changeset: 4:3f41bc784e7e
71 changeset: 4:3f41bc784e7e
72 branch: a-branch
72 branch: a-branch
73 user: test
73 user: test
74 date: Thu Jan 01 00:00:00 1970 +0000
74 date: Thu Jan 01 00:00:00 1970 +0000
75 summary: second a
75 summary: second a
76
76
77 diff -r 292258f86fdf -r 3f41bc784e7e a
77 diff -r 292258f86fdf -r 3f41bc784e7e a
78 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
78 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
79 +++ b/a Thu Jan 01 00:00:00 1970 +0000
79 +++ b/a Thu Jan 01 00:00:00 1970 +0000
80 @@ -0,0 +1,1 @@
80 @@ -0,0 +1,1 @@
81 +b
81 +b
82
82
83 changeset: 3:292258f86fdf
83 changeset: 3:292258f86fdf
84 user: test
84 user: test
85 date: Thu Jan 01 00:00:00 1970 +0000
85 date: Thu Jan 01 00:00:00 1970 +0000
86 summary: del a
86 summary: del a
87
87
88 diff -r 94c9dd5ca9b4 -r 292258f86fdf a
88 diff -r 94c9dd5ca9b4 -r 292258f86fdf a
89 --- a/a Thu Jan 01 00:00:00 1970 +0000
89 --- a/a Thu Jan 01 00:00:00 1970 +0000
90 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
90 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000
91 @@ -1,1 +0,0 @@
91 @@ -1,1 +0,0 @@
92 -a
92 -a
93
93
94 changeset: 2:94c9dd5ca9b4
94 changeset: 2:94c9dd5ca9b4
95 user: test
95 user: test
96 date: Thu Jan 01 00:00:00 1970 +0000
96 date: Thu Jan 01 00:00:00 1970 +0000
97 summary: Added tag a-tag for changeset 5ed941583260
97 summary: Added tag a-tag for changeset 5ed941583260
98
98
99 diff -r 5ed941583260 -r 94c9dd5ca9b4 .hgtags
99 diff -r 5ed941583260 -r 94c9dd5ca9b4 .hgtags
100 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
100 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
101 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
101 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
102 @@ -0,0 +1,1 @@
102 @@ -0,0 +1,1 @@
103 +5ed941583260248620985524192fdc382ef57c36 a-tag
103 +5ed941583260248620985524192fdc382ef57c36 a-tag
104
104
105 changeset: 1:5ed941583260
105 changeset: 1:5ed941583260
106 bookmark: a-bookmark
106 bookmark: a-bookmark
107 tag: a-tag
107 tag: a-tag
108 user: test
108 user: test
109 date: Thu Jan 01 00:00:00 1970 +0000
109 date: Thu Jan 01 00:00:00 1970 +0000
110 summary: first a
110 summary: first a
111
111
112 diff -r 6563da9dcf87 -r 5ed941583260 a
112 diff -r 6563da9dcf87 -r 5ed941583260 a
113 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
113 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
114 +++ b/a Thu Jan 01 00:00:00 1970 +0000
114 +++ b/a Thu Jan 01 00:00:00 1970 +0000
115 @@ -0,0 +1,1 @@
115 @@ -0,0 +1,1 @@
116 +a
116 +a
117
117
118 changeset: 0:6563da9dcf87
118 changeset: 0:6563da9dcf87
119 user: test
119 user: test
120 date: Thu Jan 01 00:00:00 1970 +0000
120 date: Thu Jan 01 00:00:00 1970 +0000
121 summary: b
121 summary: b
122
122
123 diff -r 000000000000 -r 6563da9dcf87 b
123 diff -r 000000000000 -r 6563da9dcf87 b
124 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
124 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
125 +++ b/b Thu Jan 01 00:00:00 1970 +0000
125 +++ b/b Thu Jan 01 00:00:00 1970 +0000
126 @@ -0,0 +1,1 @@
126 @@ -0,0 +1,1 @@
127 +b
127 +b
128
128
129 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
129 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -E errors.log
130 $ cat hg.pid >> $DAEMON_PIDS
130 $ cat hg.pid >> $DAEMON_PIDS
131
131
132 tip - two revisions
132 tip - two revisions
133
133
134 $ (get-with-headers.py localhost:$HGPORT 'log/tip/a')
134 $ (get-with-headers.py localhost:$HGPORT 'log/tip/a')
135 200 Script output follows
135 200 Script output follows
136
136
137 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
137 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
138 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
138 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
139 <head>
139 <head>
140 <link rel="icon" href="/static/hgicon.png" type="image/png" />
140 <link rel="icon" href="/static/hgicon.png" type="image/png" />
141 <meta name="robots" content="index, nofollow" />
141 <meta name="robots" content="index, nofollow" />
142 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
142 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
143 <script type="text/javascript" src="/static/mercurial.js"></script>
143 <script type="text/javascript" src="/static/mercurial.js"></script>
144
144
145 <title>test: a history</title>
145 <title>test: a history</title>
146 <link rel="alternate" type="application/atom+xml"
146 <link rel="alternate" type="application/atom+xml"
147 href="/atom-log/tip/a" title="Atom feed for test:a" />
147 href="/atom-log/tip/a" title="Atom feed for test:a" />
148 <link rel="alternate" type="application/rss+xml"
148 <link rel="alternate" type="application/rss+xml"
149 href="/rss-log/tip/a" title="RSS feed for test:a" />
149 href="/rss-log/tip/a" title="RSS feed for test:a" />
150 </head>
150 </head>
151 <body>
151 <body>
152
152
153 <div class="container">
153 <div class="container">
154 <div class="menu">
154 <div class="menu">
155 <div class="logo">
155 <div class="logo">
156 <a href="https://mercurial-scm.org/">
156 <a href="https://mercurial-scm.org/">
157 <img src="/static/hglogo.png" alt="mercurial" /></a>
157 <img src="/static/hglogo.png" alt="mercurial" /></a>
158 </div>
158 </div>
159 <ul>
159 <ul>
160 <li><a href="/shortlog/tip">log</a></li>
160 <li><a href="/shortlog/tip">log</a></li>
161 <li><a href="/graph/tip">graph</a></li>
161 <li><a href="/graph/tip">graph</a></li>
162 <li><a href="/tags">tags</a></li>
162 <li><a href="/tags">tags</a></li>
163 <li><a href="/bookmarks">bookmarks</a></li>
163 <li><a href="/bookmarks">bookmarks</a></li>
164 <li><a href="/branches">branches</a></li>
164 <li><a href="/branches">branches</a></li>
165 </ul>
165 </ul>
166 <ul>
166 <ul>
167 <li><a href="/rev/tip">changeset</a></li>
167 <li><a href="/rev/tip">changeset</a></li>
168 <li><a href="/file/tip">browse</a></li>
168 <li><a href="/file/tip">browse</a></li>
169 </ul>
169 </ul>
170 <ul>
170 <ul>
171 <li><a href="/file/tip/a">file</a></li>
171 <li><a href="/file/tip/a">file</a></li>
172 <li><a href="/diff/tip/a">diff</a></li>
172 <li><a href="/diff/tip/a">diff</a></li>
173 <li><a href="/comparison/tip/a">comparison</a></li>
173 <li><a href="/comparison/tip/a">comparison</a></li>
174 <li><a href="/annotate/tip/a">annotate</a></li>
174 <li><a href="/annotate/tip/a">annotate</a></li>
175 <li class="active">file log</li>
175 <li class="active">file log</li>
176 <li><a href="/raw-file/tip/a">raw</a></li>
176 <li><a href="/raw-file/tip/a">raw</a></li>
177 </ul>
177 </ul>
178 <ul>
178 <ul>
179 <li><a href="/help">help</a></li>
179 <li><a href="/help">help</a></li>
180 </ul>
180 </ul>
181 <div class="atom-logo">
181 <div class="atom-logo">
182 <a href="/atom-log/tip/a" title="subscribe to atom feed">
182 <a href="/atom-log/tip/a" title="subscribe to atom feed">
183 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
183 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
184 </a>
184 </a>
185 </div>
185 </div>
186 </div>
186 </div>
187
187
188 <div class="main">
188 <div class="main">
189 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
189 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
190 <h3>
190 <h3>
191 log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
191 log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
192 <span class="branchname">a-branch</span>
192 <span class="branchname">a-branch</span>
193
193 </h3>
194 </h3>
194
195
195 <form class="search" action="/log">
196 <form class="search" action="/log">
196
197
197 <p><input name="rev" id="search1" type="text" size="30" /></p>
198 <p><input name="rev" id="search1" type="text" size="30" /></p>
198 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
199 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
199 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
200 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
200 </form>
201 </form>
201
202
202 <div class="navigate">
203 <div class="navigate">
203 <a href="/log/tip/a?revcount=30">less</a>
204 <a href="/log/tip/a?revcount=30">less</a>
204 <a href="/log/tip/a?revcount=120">more</a>
205 <a href="/log/tip/a?revcount=120">more</a>
205 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
206 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
206
207
207 <table class="bigtable">
208 <table class="bigtable">
208 <thead>
209 <thead>
209 <tr>
210 <tr>
210 <th class="age">age</th>
211 <th class="age">age</th>
211 <th class="author">author</th>
212 <th class="author">author</th>
212 <th class="description">description</th>
213 <th class="description">description</th>
213 </tr>
214 </tr>
214 </thead>
215 </thead>
215 <tbody class="stripes2">
216 <tbody class="stripes2">
216 <tr>
217 <tr>
217 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
218 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
218 <td class="author">test</td>
219 <td class="author">test</td>
219 <td class="description">
220 <td class="description">
220 <a href="/rev/3f41bc784e7e">second a</a>
221 <a href="/rev/3f41bc784e7e">second a</a>
221 <span class="branchname">a-branch</span>
222 <span class="branchname">a-branch</span>
222 </td>
223 </td>
223 </tr>
224 </tr>
224
225
225 <tr>
226 <tr>
226 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
227 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
227 <td class="author">test</td>
228 <td class="author">test</td>
228 <td class="description">
229 <td class="description">
229 <a href="/rev/5ed941583260">first a</a>
230 <a href="/rev/5ed941583260">first a</a>
230 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
231 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
231 </td>
232 </td>
232 </tr>
233 </tr>
233
234
234
235
235 </tbody>
236 </tbody>
236 </table>
237 </table>
237
238
238 <div class="navigate">
239 <div class="navigate">
239 <a href="/log/tip/a?revcount=30">less</a>
240 <a href="/log/tip/a?revcount=30">less</a>
240 <a href="/log/tip/a?revcount=120">more</a>
241 <a href="/log/tip/a?revcount=120">more</a>
241 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
242 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
242 </div>
243 </div>
243
244
244 </div>
245 </div>
245 </div>
246 </div>
246
247
247
248
248
249
249 </body>
250 </body>
250 </html>
251 </html>
251
252
252
253
253 second version - two revisions
254 second version - two revisions
254
255
255 $ (get-with-headers.py localhost:$HGPORT 'log/4/a')
256 $ (get-with-headers.py localhost:$HGPORT 'log/4/a')
256 200 Script output follows
257 200 Script output follows
257
258
258 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
259 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
259 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
260 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
260 <head>
261 <head>
261 <link rel="icon" href="/static/hgicon.png" type="image/png" />
262 <link rel="icon" href="/static/hgicon.png" type="image/png" />
262 <meta name="robots" content="index, nofollow" />
263 <meta name="robots" content="index, nofollow" />
263 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
264 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
264 <script type="text/javascript" src="/static/mercurial.js"></script>
265 <script type="text/javascript" src="/static/mercurial.js"></script>
265
266
266 <title>test: a history</title>
267 <title>test: a history</title>
267 <link rel="alternate" type="application/atom+xml"
268 <link rel="alternate" type="application/atom+xml"
268 href="/atom-log/tip/a" title="Atom feed for test:a" />
269 href="/atom-log/tip/a" title="Atom feed for test:a" />
269 <link rel="alternate" type="application/rss+xml"
270 <link rel="alternate" type="application/rss+xml"
270 href="/rss-log/tip/a" title="RSS feed for test:a" />
271 href="/rss-log/tip/a" title="RSS feed for test:a" />
271 </head>
272 </head>
272 <body>
273 <body>
273
274
274 <div class="container">
275 <div class="container">
275 <div class="menu">
276 <div class="menu">
276 <div class="logo">
277 <div class="logo">
277 <a href="https://mercurial-scm.org/">
278 <a href="https://mercurial-scm.org/">
278 <img src="/static/hglogo.png" alt="mercurial" /></a>
279 <img src="/static/hglogo.png" alt="mercurial" /></a>
279 </div>
280 </div>
280 <ul>
281 <ul>
281 <li><a href="/shortlog/4">log</a></li>
282 <li><a href="/shortlog/4">log</a></li>
282 <li><a href="/graph/4">graph</a></li>
283 <li><a href="/graph/4">graph</a></li>
283 <li><a href="/tags">tags</a></li>
284 <li><a href="/tags">tags</a></li>
284 <li><a href="/bookmarks">bookmarks</a></li>
285 <li><a href="/bookmarks">bookmarks</a></li>
285 <li><a href="/branches">branches</a></li>
286 <li><a href="/branches">branches</a></li>
286 </ul>
287 </ul>
287 <ul>
288 <ul>
288 <li><a href="/rev/4">changeset</a></li>
289 <li><a href="/rev/4">changeset</a></li>
289 <li><a href="/file/4">browse</a></li>
290 <li><a href="/file/4">browse</a></li>
290 </ul>
291 </ul>
291 <ul>
292 <ul>
292 <li><a href="/file/4/a">file</a></li>
293 <li><a href="/file/4/a">file</a></li>
293 <li><a href="/diff/4/a">diff</a></li>
294 <li><a href="/diff/4/a">diff</a></li>
294 <li><a href="/comparison/4/a">comparison</a></li>
295 <li><a href="/comparison/4/a">comparison</a></li>
295 <li><a href="/annotate/4/a">annotate</a></li>
296 <li><a href="/annotate/4/a">annotate</a></li>
296 <li class="active">file log</li>
297 <li class="active">file log</li>
297 <li><a href="/raw-file/4/a">raw</a></li>
298 <li><a href="/raw-file/4/a">raw</a></li>
298 </ul>
299 </ul>
299 <ul>
300 <ul>
300 <li><a href="/help">help</a></li>
301 <li><a href="/help">help</a></li>
301 </ul>
302 </ul>
302 <div class="atom-logo">
303 <div class="atom-logo">
303 <a href="/atom-log/tip/a" title="subscribe to atom feed">
304 <a href="/atom-log/tip/a" title="subscribe to atom feed">
304 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
305 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
305 </a>
306 </a>
306 </div>
307 </div>
307 </div>
308 </div>
308
309
309 <div class="main">
310 <div class="main">
310 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
311 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
311 <h3>
312 <h3>
312 log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
313 log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
313 <span class="branchname">a-branch</span>
314 <span class="branchname">a-branch</span>
315
314 </h3>
316 </h3>
315
317
316 <form class="search" action="/log">
318 <form class="search" action="/log">
317
319
318 <p><input name="rev" id="search1" type="text" size="30" /></p>
320 <p><input name="rev" id="search1" type="text" size="30" /></p>
319 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
321 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
320 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
322 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
321 </form>
323 </form>
322
324
323 <div class="navigate">
325 <div class="navigate">
324 <a href="/log/4/a?revcount=30">less</a>
326 <a href="/log/4/a?revcount=30">less</a>
325 <a href="/log/4/a?revcount=120">more</a>
327 <a href="/log/4/a?revcount=120">more</a>
326 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
328 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
327
329
328 <table class="bigtable">
330 <table class="bigtable">
329 <thead>
331 <thead>
330 <tr>
332 <tr>
331 <th class="age">age</th>
333 <th class="age">age</th>
332 <th class="author">author</th>
334 <th class="author">author</th>
333 <th class="description">description</th>
335 <th class="description">description</th>
334 </tr>
336 </tr>
335 </thead>
337 </thead>
336 <tbody class="stripes2">
338 <tbody class="stripes2">
337 <tr>
339 <tr>
338 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
340 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
339 <td class="author">test</td>
341 <td class="author">test</td>
340 <td class="description">
342 <td class="description">
341 <a href="/rev/3f41bc784e7e">second a</a>
343 <a href="/rev/3f41bc784e7e">second a</a>
342 <span class="branchname">a-branch</span>
344 <span class="branchname">a-branch</span>
343 </td>
345 </td>
344 </tr>
346 </tr>
345
347
346 <tr>
348 <tr>
347 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
349 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
348 <td class="author">test</td>
350 <td class="author">test</td>
349 <td class="description">
351 <td class="description">
350 <a href="/rev/5ed941583260">first a</a>
352 <a href="/rev/5ed941583260">first a</a>
351 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
353 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
352 </td>
354 </td>
353 </tr>
355 </tr>
354
356
355
357
356 </tbody>
358 </tbody>
357 </table>
359 </table>
358
360
359 <div class="navigate">
361 <div class="navigate">
360 <a href="/log/4/a?revcount=30">less</a>
362 <a href="/log/4/a?revcount=30">less</a>
361 <a href="/log/4/a?revcount=120">more</a>
363 <a href="/log/4/a?revcount=120">more</a>
362 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
364 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
363 </div>
365 </div>
364
366
365 </div>
367 </div>
366 </div>
368 </div>
367
369
368
370
369
371
370 </body>
372 </body>
371 </html>
373 </html>
372
374
373
375
374 first deleted - one revision
376 first deleted - one revision
375
377
376 $ (get-with-headers.py localhost:$HGPORT 'log/3/a')
378 $ (get-with-headers.py localhost:$HGPORT 'log/3/a')
377 200 Script output follows
379 200 Script output follows
378
380
379 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
381 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
380 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
382 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
381 <head>
383 <head>
382 <link rel="icon" href="/static/hgicon.png" type="image/png" />
384 <link rel="icon" href="/static/hgicon.png" type="image/png" />
383 <meta name="robots" content="index, nofollow" />
385 <meta name="robots" content="index, nofollow" />
384 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
386 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
385 <script type="text/javascript" src="/static/mercurial.js"></script>
387 <script type="text/javascript" src="/static/mercurial.js"></script>
386
388
387 <title>test: a history</title>
389 <title>test: a history</title>
388 <link rel="alternate" type="application/atom+xml"
390 <link rel="alternate" type="application/atom+xml"
389 href="/atom-log/tip/a" title="Atom feed for test:a" />
391 href="/atom-log/tip/a" title="Atom feed for test:a" />
390 <link rel="alternate" type="application/rss+xml"
392 <link rel="alternate" type="application/rss+xml"
391 href="/rss-log/tip/a" title="RSS feed for test:a" />
393 href="/rss-log/tip/a" title="RSS feed for test:a" />
392 </head>
394 </head>
393 <body>
395 <body>
394
396
395 <div class="container">
397 <div class="container">
396 <div class="menu">
398 <div class="menu">
397 <div class="logo">
399 <div class="logo">
398 <a href="https://mercurial-scm.org/">
400 <a href="https://mercurial-scm.org/">
399 <img src="/static/hglogo.png" alt="mercurial" /></a>
401 <img src="/static/hglogo.png" alt="mercurial" /></a>
400 </div>
402 </div>
401 <ul>
403 <ul>
402 <li><a href="/shortlog/3">log</a></li>
404 <li><a href="/shortlog/3">log</a></li>
403 <li><a href="/graph/3">graph</a></li>
405 <li><a href="/graph/3">graph</a></li>
404 <li><a href="/tags">tags</a></li>
406 <li><a href="/tags">tags</a></li>
405 <li><a href="/bookmarks">bookmarks</a></li>
407 <li><a href="/bookmarks">bookmarks</a></li>
406 <li><a href="/branches">branches</a></li>
408 <li><a href="/branches">branches</a></li>
407 </ul>
409 </ul>
408 <ul>
410 <ul>
409 <li><a href="/rev/3">changeset</a></li>
411 <li><a href="/rev/3">changeset</a></li>
410 <li><a href="/file/3">browse</a></li>
412 <li><a href="/file/3">browse</a></li>
411 </ul>
413 </ul>
412 <ul>
414 <ul>
413 <li><a href="/file/3/a">file</a></li>
415 <li><a href="/file/3/a">file</a></li>
414 <li><a href="/diff/3/a">diff</a></li>
416 <li><a href="/diff/3/a">diff</a></li>
415 <li><a href="/comparison/3/a">comparison</a></li>
417 <li><a href="/comparison/3/a">comparison</a></li>
416 <li><a href="/annotate/3/a">annotate</a></li>
418 <li><a href="/annotate/3/a">annotate</a></li>
417 <li class="active">file log</li>
419 <li class="active">file log</li>
418 <li><a href="/raw-file/3/a">raw</a></li>
420 <li><a href="/raw-file/3/a">raw</a></li>
419 </ul>
421 </ul>
420 <ul>
422 <ul>
421 <li><a href="/help">help</a></li>
423 <li><a href="/help">help</a></li>
422 </ul>
424 </ul>
423 <div class="atom-logo">
425 <div class="atom-logo">
424 <a href="/atom-log/tip/a" title="subscribe to atom feed">
426 <a href="/atom-log/tip/a" title="subscribe to atom feed">
425 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
427 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
426 </a>
428 </a>
427 </div>
429 </div>
428 </div>
430 </div>
429
431
430 <div class="main">
432 <div class="main">
431 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
433 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
432 <h3>
434 <h3>
433 log a @ 1:<a href="/rev/5ed941583260">5ed941583260</a>
435 log a @ 1:<a href="/rev/5ed941583260">5ed941583260</a>
434 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
436 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
437
435 </h3>
438 </h3>
436
439
437 <form class="search" action="/log">
440 <form class="search" action="/log">
438
441
439 <p><input name="rev" id="search1" type="text" size="30" /></p>
442 <p><input name="rev" id="search1" type="text" size="30" /></p>
440 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
443 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
441 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
444 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
442 </form>
445 </form>
443
446
444 <div class="navigate">
447 <div class="navigate">
445 <a href="/log/3/a?revcount=30">less</a>
448 <a href="/log/3/a?revcount=30">less</a>
446 <a href="/log/3/a?revcount=120">more</a>
449 <a href="/log/3/a?revcount=120">more</a>
447 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
450 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
448
451
449 <table class="bigtable">
452 <table class="bigtable">
450 <thead>
453 <thead>
451 <tr>
454 <tr>
452 <th class="age">age</th>
455 <th class="age">age</th>
453 <th class="author">author</th>
456 <th class="author">author</th>
454 <th class="description">description</th>
457 <th class="description">description</th>
455 </tr>
458 </tr>
456 </thead>
459 </thead>
457 <tbody class="stripes2">
460 <tbody class="stripes2">
458 <tr>
461 <tr>
459 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
462 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
460 <td class="author">test</td>
463 <td class="author">test</td>
461 <td class="description">
464 <td class="description">
462 <a href="/rev/5ed941583260">first a</a>
465 <a href="/rev/5ed941583260">first a</a>
463 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
466 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
464 </td>
467 </td>
465 </tr>
468 </tr>
466
469
467
470
468 </tbody>
471 </tbody>
469 </table>
472 </table>
470
473
471 <div class="navigate">
474 <div class="navigate">
472 <a href="/log/3/a?revcount=30">less</a>
475 <a href="/log/3/a?revcount=30">less</a>
473 <a href="/log/3/a?revcount=120">more</a>
476 <a href="/log/3/a?revcount=120">more</a>
474 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
477 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
475 </div>
478 </div>
476
479
477 </div>
480 </div>
478 </div>
481 </div>
479
482
480
483
481
484
482 </body>
485 </body>
483 </html>
486 </html>
484
487
485
488
486 first version - one revision
489 first version - one revision
487
490
488 $ (get-with-headers.py localhost:$HGPORT 'log/1/a')
491 $ (get-with-headers.py localhost:$HGPORT 'log/1/a')
489 200 Script output follows
492 200 Script output follows
490
493
491 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
494 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
492 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
495 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
493 <head>
496 <head>
494 <link rel="icon" href="/static/hgicon.png" type="image/png" />
497 <link rel="icon" href="/static/hgicon.png" type="image/png" />
495 <meta name="robots" content="index, nofollow" />
498 <meta name="robots" content="index, nofollow" />
496 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
499 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
497 <script type="text/javascript" src="/static/mercurial.js"></script>
500 <script type="text/javascript" src="/static/mercurial.js"></script>
498
501
499 <title>test: a history</title>
502 <title>test: a history</title>
500 <link rel="alternate" type="application/atom+xml"
503 <link rel="alternate" type="application/atom+xml"
501 href="/atom-log/tip/a" title="Atom feed for test:a" />
504 href="/atom-log/tip/a" title="Atom feed for test:a" />
502 <link rel="alternate" type="application/rss+xml"
505 <link rel="alternate" type="application/rss+xml"
503 href="/rss-log/tip/a" title="RSS feed for test:a" />
506 href="/rss-log/tip/a" title="RSS feed for test:a" />
504 </head>
507 </head>
505 <body>
508 <body>
506
509
507 <div class="container">
510 <div class="container">
508 <div class="menu">
511 <div class="menu">
509 <div class="logo">
512 <div class="logo">
510 <a href="https://mercurial-scm.org/">
513 <a href="https://mercurial-scm.org/">
511 <img src="/static/hglogo.png" alt="mercurial" /></a>
514 <img src="/static/hglogo.png" alt="mercurial" /></a>
512 </div>
515 </div>
513 <ul>
516 <ul>
514 <li><a href="/shortlog/1">log</a></li>
517 <li><a href="/shortlog/1">log</a></li>
515 <li><a href="/graph/1">graph</a></li>
518 <li><a href="/graph/1">graph</a></li>
516 <li><a href="/tags">tags</a></li>
519 <li><a href="/tags">tags</a></li>
517 <li><a href="/bookmarks">bookmarks</a></li>
520 <li><a href="/bookmarks">bookmarks</a></li>
518 <li><a href="/branches">branches</a></li>
521 <li><a href="/branches">branches</a></li>
519 </ul>
522 </ul>
520 <ul>
523 <ul>
521 <li><a href="/rev/1">changeset</a></li>
524 <li><a href="/rev/1">changeset</a></li>
522 <li><a href="/file/1">browse</a></li>
525 <li><a href="/file/1">browse</a></li>
523 </ul>
526 </ul>
524 <ul>
527 <ul>
525 <li><a href="/file/1/a">file</a></li>
528 <li><a href="/file/1/a">file</a></li>
526 <li><a href="/diff/1/a">diff</a></li>
529 <li><a href="/diff/1/a">diff</a></li>
527 <li><a href="/comparison/1/a">comparison</a></li>
530 <li><a href="/comparison/1/a">comparison</a></li>
528 <li><a href="/annotate/1/a">annotate</a></li>
531 <li><a href="/annotate/1/a">annotate</a></li>
529 <li class="active">file log</li>
532 <li class="active">file log</li>
530 <li><a href="/raw-file/1/a">raw</a></li>
533 <li><a href="/raw-file/1/a">raw</a></li>
531 </ul>
534 </ul>
532 <ul>
535 <ul>
533 <li><a href="/help">help</a></li>
536 <li><a href="/help">help</a></li>
534 </ul>
537 </ul>
535 <div class="atom-logo">
538 <div class="atom-logo">
536 <a href="/atom-log/tip/a" title="subscribe to atom feed">
539 <a href="/atom-log/tip/a" title="subscribe to atom feed">
537 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
540 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
538 </a>
541 </a>
539 </div>
542 </div>
540 </div>
543 </div>
541
544
542 <div class="main">
545 <div class="main">
543 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
546 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
544 <h3>
547 <h3>
545 log a @ 1:<a href="/rev/5ed941583260">5ed941583260</a>
548 log a @ 1:<a href="/rev/5ed941583260">5ed941583260</a>
546 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
549 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
550
547 </h3>
551 </h3>
548
552
549 <form class="search" action="/log">
553 <form class="search" action="/log">
550
554
551 <p><input name="rev" id="search1" type="text" size="30" /></p>
555 <p><input name="rev" id="search1" type="text" size="30" /></p>
552 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
556 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
553 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
557 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
554 </form>
558 </form>
555
559
556 <div class="navigate">
560 <div class="navigate">
557 <a href="/log/1/a?revcount=30">less</a>
561 <a href="/log/1/a?revcount=30">less</a>
558 <a href="/log/1/a?revcount=120">more</a>
562 <a href="/log/1/a?revcount=120">more</a>
559 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
563 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
560
564
561 <table class="bigtable">
565 <table class="bigtable">
562 <thead>
566 <thead>
563 <tr>
567 <tr>
564 <th class="age">age</th>
568 <th class="age">age</th>
565 <th class="author">author</th>
569 <th class="author">author</th>
566 <th class="description">description</th>
570 <th class="description">description</th>
567 </tr>
571 </tr>
568 </thead>
572 </thead>
569 <tbody class="stripes2">
573 <tbody class="stripes2">
570 <tr>
574 <tr>
571 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
575 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
572 <td class="author">test</td>
576 <td class="author">test</td>
573 <td class="description">
577 <td class="description">
574 <a href="/rev/5ed941583260">first a</a>
578 <a href="/rev/5ed941583260">first a</a>
575 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
579 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
576 </td>
580 </td>
577 </tr>
581 </tr>
578
582
579
583
580 </tbody>
584 </tbody>
581 </table>
585 </table>
582
586
583 <div class="navigate">
587 <div class="navigate">
584 <a href="/log/1/a?revcount=30">less</a>
588 <a href="/log/1/a?revcount=30">less</a>
585 <a href="/log/1/a?revcount=120">more</a>
589 <a href="/log/1/a?revcount=120">more</a>
586 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
590 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
587 </div>
591 </div>
588
592
589 </div>
593 </div>
590 </div>
594 </div>
591
595
592
596
593
597
594 </body>
598 </body>
595 </html>
599 </html>
596
600
597
601
598 before addition - error
602 before addition - error
599
603
600 $ (get-with-headers.py localhost:$HGPORT 'log/0/a')
604 $ (get-with-headers.py localhost:$HGPORT 'log/0/a')
601 404 Not Found
605 404 Not Found
602
606
603 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
607 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
604 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
608 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
605 <head>
609 <head>
606 <link rel="icon" href="/static/hgicon.png" type="image/png" />
610 <link rel="icon" href="/static/hgicon.png" type="image/png" />
607 <meta name="robots" content="index, nofollow" />
611 <meta name="robots" content="index, nofollow" />
608 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
612 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
609 <script type="text/javascript" src="/static/mercurial.js"></script>
613 <script type="text/javascript" src="/static/mercurial.js"></script>
610
614
611 <title>test: error</title>
615 <title>test: error</title>
612 </head>
616 </head>
613 <body>
617 <body>
614
618
615 <div class="container">
619 <div class="container">
616 <div class="menu">
620 <div class="menu">
617 <div class="logo">
621 <div class="logo">
618 <a href="https://mercurial-scm.org/">
622 <a href="https://mercurial-scm.org/">
619 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
623 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
620 </div>
624 </div>
621 <ul>
625 <ul>
622 <li><a href="/shortlog">log</a></li>
626 <li><a href="/shortlog">log</a></li>
623 <li><a href="/graph">graph</a></li>
627 <li><a href="/graph">graph</a></li>
624 <li><a href="/tags">tags</a></li>
628 <li><a href="/tags">tags</a></li>
625 <li><a href="/bookmarks">bookmarks</a></li>
629 <li><a href="/bookmarks">bookmarks</a></li>
626 <li><a href="/branches">branches</a></li>
630 <li><a href="/branches">branches</a></li>
627 </ul>
631 </ul>
628 <ul>
632 <ul>
629 <li><a href="/help">help</a></li>
633 <li><a href="/help">help</a></li>
630 </ul>
634 </ul>
631 </div>
635 </div>
632
636
633 <div class="main">
637 <div class="main">
634
638
635 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
639 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
636 <h3>error</h3>
640 <h3>error</h3>
637
641
638 <form class="search" action="/log">
642 <form class="search" action="/log">
639
643
640 <p><input name="rev" id="search1" type="text" size="30"></p>
644 <p><input name="rev" id="search1" type="text" size="30"></p>
641 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
645 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
642 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
646 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
643 </form>
647 </form>
644
648
645 <div class="description">
649 <div class="description">
646 <p>
650 <p>
647 An error occurred while processing your request:
651 An error occurred while processing your request:
648 </p>
652 </p>
649 <p>
653 <p>
650 a@6563da9dcf87: not found in manifest
654 a@6563da9dcf87: not found in manifest
651 </p>
655 </p>
652 </div>
656 </div>
653 </div>
657 </div>
654 </div>
658 </div>
655
659
656
660
657
661
658 </body>
662 </body>
659 </html>
663 </html>
660
664
661 [1]
665 [1]
662
666
667 $ hg log -r 'followlines(c, 1:2, startrev=tip) and follow(c)'
668 changeset: 0:6563da9dcf87
669 user: test
670 date: Thu Jan 01 00:00:00 1970 +0000
671 summary: b
672
673 changeset: 7:46c1a66bd8fc
674 branch: a-branch
675 tag: tip
676 user: test
677 date: Thu Jan 01 00:00:00 1970 +0000
678 summary: change c
679
680 $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?linerange=1:2')
681 200 Script output follows
682
683 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
684 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
685 <head>
686 <link rel="icon" href="/static/hgicon.png" type="image/png" />
687 <meta name="robots" content="index, nofollow" />
688 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
689 <script type="text/javascript" src="/static/mercurial.js"></script>
690
691 <title>test: c history</title>
692 <link rel="alternate" type="application/atom+xml"
693 href="/atom-log/tip/c" title="Atom feed for test:c" />
694 <link rel="alternate" type="application/rss+xml"
695 href="/rss-log/tip/c" title="RSS feed for test:c" />
696 </head>
697 <body>
698
699 <div class="container">
700 <div class="menu">
701 <div class="logo">
702 <a href="https://mercurial-scm.org/">
703 <img src="/static/hglogo.png" alt="mercurial" /></a>
704 </div>
705 <ul>
706 <li><a href="/shortlog/tip">log</a></li>
707 <li><a href="/graph/tip">graph</a></li>
708 <li><a href="/tags">tags</a></li>
709 <li><a href="/bookmarks">bookmarks</a></li>
710 <li><a href="/branches">branches</a></li>
711 </ul>
712 <ul>
713 <li><a href="/rev/tip">changeset</a></li>
714 <li><a href="/file/tip">browse</a></li>
715 </ul>
716 <ul>
717 <li><a href="/file/tip/c">file</a></li>
718 <li><a href="/diff/tip/c">diff</a></li>
719 <li><a href="/comparison/tip/c">comparison</a></li>
720 <li><a href="/annotate/tip/c">annotate</a></li>
721 <li class="active">file log</li>
722 <li><a href="/raw-file/tip/c">raw</a></li>
723 </ul>
724 <ul>
725 <li><a href="/help">help</a></li>
726 </ul>
727 <div class="atom-logo">
728 <a href="/atom-log/tip/c" title="subscribe to atom feed">
729 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
730 </a>
731 </div>
732 </div>
733
734 <div class="main">
735 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
736 <h3>
737 log c @ 7:<a href="/rev/46c1a66bd8fc">46c1a66bd8fc</a>
738 <span class="branchname">a-branch</span> <span class="tag">tip</span>
739 (following lines 1:2 <a href="/log/tip/c">back to filelog</a>)
740 </h3>
741
742 <form class="search" action="/log">
743
744 <p><input name="rev" id="search1" type="text" size="30" /></p>
745 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
746 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
747 </form>
748
749 <div class="navigate">
750 <a href="/log/tip/c?linerange=1%3A2&revcount=30">less</a>
751 <a href="/log/tip/c?linerange=1%3A2&revcount=120">more</a>
752 | </div>
753
754 <table class="bigtable">
755 <thead>
756 <tr>
757 <th class="age">age</th>
758 <th class="author">author</th>
759 <th class="description">description</th>
760 </tr>
761 </thead>
762 <tbody class="stripes2">
763 <tr>
764 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
765 <td class="author">test</td>
766 <td class="description">
767 <a href="/rev/46c1a66bd8fc">change c</a>
768 <span class="branchhead">a-branch</span> <span class="tag">tip</span>
769 </td>
770 </tr>
771
772 <tr>
773 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
774 <td class="author">test</td>
775 <td class="description">
776 <a href="/rev/6563da9dcf87">b</a>
777
778 </td>
779 </tr>
780
781
782 </tbody>
783 </table>
784
785 <div class="navigate">
786 <a href="/log/tip/c?linerange=1%3A2&revcount=30">less</a>
787 <a href="/log/tip/c?linerange=1%3A2&revcount=120">more</a>
788 |
789 </div>
790
791 </div>
792 </div>
793
794
795
796 </body>
797 </html>
798
799 $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?linerange=1%3A2&revcount=1')
800 200 Script output follows
801
802 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
803 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
804 <head>
805 <link rel="icon" href="/static/hgicon.png" type="image/png" />
806 <meta name="robots" content="index, nofollow" />
807 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
808 <script type="text/javascript" src="/static/mercurial.js"></script>
809
810 <title>test: c history</title>
811 <link rel="alternate" type="application/atom+xml"
812 href="/atom-log/tip/c" title="Atom feed for test:c" />
813 <link rel="alternate" type="application/rss+xml"
814 href="/rss-log/tip/c" title="RSS feed for test:c" />
815 </head>
816 <body>
817
818 <div class="container">
819 <div class="menu">
820 <div class="logo">
821 <a href="https://mercurial-scm.org/">
822 <img src="/static/hglogo.png" alt="mercurial" /></a>
823 </div>
824 <ul>
825 <li><a href="/shortlog/tip?revcount=1">log</a></li>
826 <li><a href="/graph/tip?revcount=1">graph</a></li>
827 <li><a href="/tags?revcount=1">tags</a></li>
828 <li><a href="/bookmarks?revcount=1">bookmarks</a></li>
829 <li><a href="/branches?revcount=1">branches</a></li>
830 </ul>
831 <ul>
832 <li><a href="/rev/tip?revcount=1">changeset</a></li>
833 <li><a href="/file/tip?revcount=1">browse</a></li>
834 </ul>
835 <ul>
836 <li><a href="/file/tip/c?revcount=1">file</a></li>
837 <li><a href="/diff/tip/c?revcount=1">diff</a></li>
838 <li><a href="/comparison/tip/c?revcount=1">comparison</a></li>
839 <li><a href="/annotate/tip/c?revcount=1">annotate</a></li>
840 <li class="active">file log</li>
841 <li><a href="/raw-file/tip/c">raw</a></li>
842 </ul>
843 <ul>
844 <li><a href="/help?revcount=1">help</a></li>
845 </ul>
846 <div class="atom-logo">
847 <a href="/atom-log/tip/c" title="subscribe to atom feed">
848 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
849 </a>
850 </div>
851 </div>
852
853 <div class="main">
854 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
855 <h3>
856 log c @ 7:<a href="/rev/46c1a66bd8fc?revcount=1">46c1a66bd8fc</a>
857 <span class="branchname">a-branch</span> <span class="tag">tip</span>
858 (following lines 1:2 <a href="/log/tip/c?revcount=1">back to filelog</a>)
859 </h3>
860
861 <form class="search" action="/log">
862 <input type="hidden" name="revcount" value="1" />
863 <p><input name="rev" id="search1" type="text" size="30" /></p>
864 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
865 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
866 </form>
867
868 <div class="navigate">
869 <a href="/log/tip/c?linerange=1%3A2&revcount=1">less</a>
870 <a href="/log/tip/c?linerange=1%3A2&revcount=2">more</a>
871 | </div>
872
873 <table class="bigtable">
874 <thead>
875 <tr>
876 <th class="age">age</th>
877 <th class="author">author</th>
878 <th class="description">description</th>
879 </tr>
880 </thead>
881 <tbody class="stripes2">
882 <tr>
883 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
884 <td class="author">test</td>
885 <td class="description">
886 <a href="/rev/46c1a66bd8fc?revcount=1">change c</a>
887 <span class="branchhead">a-branch</span> <span class="tag">tip</span>
888 </td>
889 </tr>
890
891
892 </tbody>
893 </table>
894
895 <div class="navigate">
896 <a href="/log/tip/c?linerange=1%3A2&revcount=1">less</a>
897 <a href="/log/tip/c?linerange=1%3A2&revcount=2">more</a>
898 |
899 </div>
900
901 </div>
902 </div>
903
904
905
906 </body>
907 </html>
908
909 $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=1' --headeronly)
910 400 invalid linerange parameter
911 [1]
912 $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=1:a' --headeronly)
913 400 invalid linerange parameter
914 [1]
915 $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=1:2&linerange=3:4' --headeronly)
916 400 redundant linerange parameter
917 [1]
918 $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=3:2' --headeronly)
919 400 line range must be positive
920 [1]
921 $ (get-with-headers.py localhost:$HGPORT 'log/3/a?linerange=0:1' --headeronly)
922 400 fromline must be strictly positive
923 [1]
924
663 should show base link, use spartan because it shows it
925 should show base link, use spartan because it shows it
664
926
665 $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?style=spartan')
927 $ (get-with-headers.py localhost:$HGPORT 'log/tip/c?style=spartan')
666 200 Script output follows
928 200 Script output follows
667
929
668 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
930 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
669 <html>
931 <html>
670 <head>
932 <head>
671 <link rel="icon" href="/static/hgicon.png" type="image/png">
933 <link rel="icon" href="/static/hgicon.png" type="image/png">
672 <meta name="robots" content="index, nofollow" />
934 <meta name="robots" content="index, nofollow" />
673 <link rel="stylesheet" href="/static/style.css" type="text/css" />
935 <link rel="stylesheet" href="/static/style.css" type="text/css" />
674 <script type="text/javascript" src="/static/mercurial.js"></script>
936 <script type="text/javascript" src="/static/mercurial.js"></script>
675
937
676 <title>test: c history</title>
938 <title>test: c history</title>
677 <link rel="alternate" type="application/atom+xml"
939 <link rel="alternate" type="application/atom+xml"
678 href="/atom-log/tip/c" title="Atom feed for test:c">
940 href="/atom-log/tip/c" title="Atom feed for test:c">
679 <link rel="alternate" type="application/rss+xml"
941 <link rel="alternate" type="application/rss+xml"
680 href="/rss-log/tip/c" title="RSS feed for test:c">
942 href="/rss-log/tip/c" title="RSS feed for test:c">
681 </head>
943 </head>
682 <body>
944 <body>
683
945
684 <div class="buttons">
946 <div class="buttons">
685 <a href="/log?style=spartan">changelog</a>
947 <a href="/log?style=spartan">changelog</a>
686 <a href="/shortlog?style=spartan">shortlog</a>
948 <a href="/shortlog?style=spartan">shortlog</a>
687 <a href="/graph?style=spartan">graph</a>
949 <a href="/graph?style=spartan">graph</a>
688 <a href="/tags?style=spartan">tags</a>
950 <a href="/tags?style=spartan">tags</a>
689 <a href="/branches?style=spartan">branches</a>
951 <a href="/branches?style=spartan">branches</a>
690 <a href="/file/tip/c?style=spartan">file</a>
952 <a href="/file/tip/c?style=spartan">file</a>
691 <a href="/annotate/tip/c?style=spartan">annotate</a>
953 <a href="/annotate/tip/c?style=spartan">annotate</a>
692 <a href="/help?style=spartan">help</a>
954 <a href="/help?style=spartan">help</a>
693 <a type="application/rss+xml" href="/rss-log/tip/c">rss</a>
955 <a type="application/rss+xml" href="/rss-log/tip/c">rss</a>
694 <a type="application/atom+xml" href="/atom-log/tip/c" title="Atom feed for test:c">atom</a>
956 <a type="application/atom+xml" href="/atom-log/tip/c" title="Atom feed for test:c">atom</a>
695 </div>
957 </div>
696
958
697 <h2><a href="/">Mercurial</a> / c revision history</h2>
959 <h2><a href="/">Mercurial</a> / c revision history</h2>
698
960
699 <p>navigate: <small class="navigate"><a href="/log/c9637d3cc8ef/c?style=spartan">(0)</a> <a href="/log/tip/c?style=spartan">tip</a> </small></p>
961 <p>navigate: <small class="navigate"><a href="/log/c9637d3cc8ef/c?style=spartan">(0)</a> <a href="/log/tip/c?style=spartan">tip</a> </small></p>
700
962
701 <table class="logEntry parity0">
963 <table class="logEntry parity0">
702 <tr>
964 <tr>
703 <th class="label"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>:</th>
965 <th class="label"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>:</th>
704 <th class="firstline"><a href="/rev/46c1a66bd8fc?style=spartan">change c</a></th>
966 <th class="firstline"><a href="/rev/46c1a66bd8fc?style=spartan">change c</a></th>
705 </tr>
967 </tr>
706 <tr>
968 <tr>
707 <th class="revision">revision 1:</th>
969 <th class="revision">revision 1:</th>
708 <td class="node">
970 <td class="node">
709 <a href="/file/46c1a66bd8fc/c?style=spartan">46c1a66bd8fc</a>
971 <a href="/file/46c1a66bd8fc/c?style=spartan">46c1a66bd8fc</a>
710 <a href="/diff/46c1a66bd8fc/c?style=spartan">(diff)</a>
972 <a href="/diff/46c1a66bd8fc/c?style=spartan">(diff)</a>
711 <a href="/annotate/46c1a66bd8fc/c?style=spartan">(annotate)</a>
973 <a href="/annotate/46c1a66bd8fc/c?style=spartan">(annotate)</a>
712 </td>
974 </td>
713 </tr>
975 </tr>
714
976
715 <tr>
977 <tr>
716 <th class="author">author:</th>
978 <th class="author">author:</th>
717 <td class="author">&#116;&#101;&#115;&#116;</td>
979 <td class="author">&#116;&#101;&#115;&#116;</td>
718 </tr>
980 </tr>
719 <tr>
981 <tr>
720 <th class="date">date:</th>
982 <th class="date">date:</th>
721 <td class="date">Thu, 01 Jan 1970 00:00:00 +0000</td>
983 <td class="date">Thu, 01 Jan 1970 00:00:00 +0000</td>
722 </tr>
984 </tr>
723 </table>
985 </table>
724
986
725
987
726 <table class="logEntry parity1">
988 <table class="logEntry parity1">
727 <tr>
989 <tr>
728 <th class="label"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>:</th>
990 <th class="label"><span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span>:</th>
729 <th class="firstline"><a href="/rev/c9637d3cc8ef?style=spartan">mv b</a></th>
991 <th class="firstline"><a href="/rev/c9637d3cc8ef?style=spartan">mv b</a></th>
730 </tr>
992 </tr>
731 <tr>
993 <tr>
732 <th class="revision">revision 0:</th>
994 <th class="revision">revision 0:</th>
733 <td class="node">
995 <td class="node">
734 <a href="/file/c9637d3cc8ef/c?style=spartan">c9637d3cc8ef</a>
996 <a href="/file/c9637d3cc8ef/c?style=spartan">c9637d3cc8ef</a>
735 <a href="/diff/c9637d3cc8ef/c?style=spartan">(diff)</a>
997 <a href="/diff/c9637d3cc8ef/c?style=spartan">(diff)</a>
736 <a href="/annotate/c9637d3cc8ef/c?style=spartan">(annotate)</a>
998 <a href="/annotate/c9637d3cc8ef/c?style=spartan">(annotate)</a>
737 </td>
999 </td>
738 </tr>
1000 </tr>
739
1001
740 <tr>
1002 <tr>
741 <th>base:</th>
1003 <th>base:</th>
742 <td>
1004 <td>
743 <a href="/file/1e88685f5dde/b?style=spartan">
1005 <a href="/file/1e88685f5dde/b?style=spartan">
744 b@1e88685f5dde
1006 b@1e88685f5dde
745 </a>
1007 </a>
746 </td>
1008 </td>
747 </tr>
1009 </tr>
748 <tr>
1010 <tr>
749 <th class="author">author:</th>
1011 <th class="author">author:</th>
750 <td class="author">&#116;&#101;&#115;&#116;</td>
1012 <td class="author">&#116;&#101;&#115;&#116;</td>
751 </tr>
1013 </tr>
752 <tr>
1014 <tr>
753 <th class="date">date:</th>
1015 <th class="date">date:</th>
754 <td class="date">Thu, 01 Jan 1970 00:00:00 +0000</td>
1016 <td class="date">Thu, 01 Jan 1970 00:00:00 +0000</td>
755 </tr>
1017 </tr>
756 </table>
1018 </table>
757
1019
758
1020
759
1021
760
1022
761
1023
762 <div class="logo">
1024 <div class="logo">
763 <a href="https://mercurial-scm.org/">
1025 <a href="https://mercurial-scm.org/">
764 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
1026 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial"></a>
765 </div>
1027 </div>
766
1028
767 </body>
1029 </body>
768 </html>
1030 </html>
769
1031
770
1032
771 filelog with patch
1033 filelog with patch
772
1034
773 $ (get-with-headers.py localhost:$HGPORT 'log/4/a?patch=1')
1035 $ (get-with-headers.py localhost:$HGPORT 'log/4/a?patch=1')
774 200 Script output follows
1036 200 Script output follows
775
1037
776 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
1038 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
777 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
1039 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
778 <head>
1040 <head>
779 <link rel="icon" href="/static/hgicon.png" type="image/png" />
1041 <link rel="icon" href="/static/hgicon.png" type="image/png" />
780 <meta name="robots" content="index, nofollow" />
1042 <meta name="robots" content="index, nofollow" />
781 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
1043 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
782 <script type="text/javascript" src="/static/mercurial.js"></script>
1044 <script type="text/javascript" src="/static/mercurial.js"></script>
783
1045
784 <title>test: a history</title>
1046 <title>test: a history</title>
785 <link rel="alternate" type="application/atom+xml"
1047 <link rel="alternate" type="application/atom+xml"
786 href="/atom-log/tip/a" title="Atom feed for test:a" />
1048 href="/atom-log/tip/a" title="Atom feed for test:a" />
787 <link rel="alternate" type="application/rss+xml"
1049 <link rel="alternate" type="application/rss+xml"
788 href="/rss-log/tip/a" title="RSS feed for test:a" />
1050 href="/rss-log/tip/a" title="RSS feed for test:a" />
789 </head>
1051 </head>
790 <body>
1052 <body>
791
1053
792 <div class="container">
1054 <div class="container">
793 <div class="menu">
1055 <div class="menu">
794 <div class="logo">
1056 <div class="logo">
795 <a href="https://mercurial-scm.org/">
1057 <a href="https://mercurial-scm.org/">
796 <img src="/static/hglogo.png" alt="mercurial" /></a>
1058 <img src="/static/hglogo.png" alt="mercurial" /></a>
797 </div>
1059 </div>
798 <ul>
1060 <ul>
799 <li><a href="/shortlog/4">log</a></li>
1061 <li><a href="/shortlog/4">log</a></li>
800 <li><a href="/graph/4">graph</a></li>
1062 <li><a href="/graph/4">graph</a></li>
801 <li><a href="/tags">tags</a></li>
1063 <li><a href="/tags">tags</a></li>
802 <li><a href="/bookmarks">bookmarks</a></li>
1064 <li><a href="/bookmarks">bookmarks</a></li>
803 <li><a href="/branches">branches</a></li>
1065 <li><a href="/branches">branches</a></li>
804 </ul>
1066 </ul>
805 <ul>
1067 <ul>
806 <li><a href="/rev/4">changeset</a></li>
1068 <li><a href="/rev/4">changeset</a></li>
807 <li><a href="/file/4">browse</a></li>
1069 <li><a href="/file/4">browse</a></li>
808 </ul>
1070 </ul>
809 <ul>
1071 <ul>
810 <li><a href="/file/4/a">file</a></li>
1072 <li><a href="/file/4/a">file</a></li>
811 <li><a href="/diff/4/a">diff</a></li>
1073 <li><a href="/diff/4/a">diff</a></li>
812 <li><a href="/comparison/4/a">comparison</a></li>
1074 <li><a href="/comparison/4/a">comparison</a></li>
813 <li><a href="/annotate/4/a">annotate</a></li>
1075 <li><a href="/annotate/4/a">annotate</a></li>
814 <li class="active">file log</li>
1076 <li class="active">file log</li>
815 <li><a href="/raw-file/4/a">raw</a></li>
1077 <li><a href="/raw-file/4/a">raw</a></li>
816 </ul>
1078 </ul>
817 <ul>
1079 <ul>
818 <li><a href="/help">help</a></li>
1080 <li><a href="/help">help</a></li>
819 </ul>
1081 </ul>
820 <div class="atom-logo">
1082 <div class="atom-logo">
821 <a href="/atom-log/tip/a" title="subscribe to atom feed">
1083 <a href="/atom-log/tip/a" title="subscribe to atom feed">
822 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
1084 <img class="atom-logo" src="/static/feed-icon-14x14.png" alt="atom feed" />
823 </a>
1085 </a>
824 </div>
1086 </div>
825 </div>
1087 </div>
826
1088
827 <div class="main">
1089 <div class="main">
828 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
1090 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
829 <h3>
1091 <h3>
830 log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
1092 log a @ 4:<a href="/rev/3f41bc784e7e">3f41bc784e7e</a>
831 <span class="branchname">a-branch</span>
1093 <span class="branchname">a-branch</span>
1094
832 </h3>
1095 </h3>
833
1096
834 <form class="search" action="/log">
1097 <form class="search" action="/log">
835
1098
836 <p><input name="rev" id="search1" type="text" size="30" /></p>
1099 <p><input name="rev" id="search1" type="text" size="30" /></p>
837 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
1100 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
838 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
1101 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
839 </form>
1102 </form>
840
1103
841 <div class="navigate">
1104 <div class="navigate">
842 <a href="/log/4/a?patch=1&revcount=30">less</a>
1105 <a href="/log/4/a?patch=1&revcount=30">less</a>
843 <a href="/log/4/a?patch=1&revcount=120">more</a>
1106 <a href="/log/4/a?patch=1&revcount=120">more</a>
844 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
1107 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a> </div>
845
1108
846 <table class="bigtable">
1109 <table class="bigtable">
847 <thead>
1110 <thead>
848 <tr>
1111 <tr>
849 <th class="age">age</th>
1112 <th class="age">age</th>
850 <th class="author">author</th>
1113 <th class="author">author</th>
851 <th class="description">description</th>
1114 <th class="description">description</th>
852 </tr>
1115 </tr>
853 </thead>
1116 </thead>
854 <tbody class="stripes2">
1117 <tbody class="stripes2">
855 <tr>
1118 <tr>
856 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
1119 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
857 <td class="author">test</td>
1120 <td class="author">test</td>
858 <td class="description">
1121 <td class="description">
859 <a href="/rev/3f41bc784e7e">second a</a>
1122 <a href="/rev/3f41bc784e7e">second a</a>
860 <span class="branchname">a-branch</span>
1123 <span class="branchname">a-branch</span>
861 </td>
1124 </td>
862 </tr>
1125 </tr>
863 <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
1126 <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
864 <span id="l1.1" class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000</span><a href="#l1.1"></a>
1127 <span id="l1.1" class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000</span><a href="#l1.1"></a>
865 <span id="l1.2" class="plusline">+++ b/a Thu Jan 01 00:00:00 1970 +0000</span><a href="#l1.2"></a>
1128 <span id="l1.2" class="plusline">+++ b/a Thu Jan 01 00:00:00 1970 +0000</span><a href="#l1.2"></a>
866 <span id="l1.3" class="atline">@@ -0,0 +1,1 @@</span><a href="#l1.3"></a>
1129 <span id="l1.3" class="atline">@@ -0,0 +1,1 @@</span><a href="#l1.3"></a>
867 <span id="l1.4" class="plusline">+b</span><a href="#l1.4"></a></pre></div></td></tr>
1130 <span id="l1.4" class="plusline">+b</span><a href="#l1.4"></a></pre></div></td></tr>
868 <tr>
1131 <tr>
869 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
1132 <td class="age">Thu, 01 Jan 1970 00:00:00 +0000</td>
870 <td class="author">test</td>
1133 <td class="author">test</td>
871 <td class="description">
1134 <td class="description">
872 <a href="/rev/5ed941583260">first a</a>
1135 <a href="/rev/5ed941583260">first a</a>
873 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
1136 <span class="tag">a-tag</span> <span class="tag">a-bookmark</span>
874 </td>
1137 </td>
875 </tr>
1138 </tr>
876 <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
1139 <tr><td colspan="3"><div class="bottomline inc-lineno"><pre class="sourcelines wrap">
877 <span id="l1.1" class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000</span><a href="#l1.1"></a>
1140 <span id="l1.1" class="minusline">--- /dev/null Thu Jan 01 00:00:00 1970 +0000</span><a href="#l1.1"></a>
878 <span id="l1.2" class="plusline">+++ b/a Thu Jan 01 00:00:00 1970 +0000</span><a href="#l1.2"></a>
1141 <span id="l1.2" class="plusline">+++ b/a Thu Jan 01 00:00:00 1970 +0000</span><a href="#l1.2"></a>
879 <span id="l1.3" class="atline">@@ -0,0 +1,1 @@</span><a href="#l1.3"></a>
1142 <span id="l1.3" class="atline">@@ -0,0 +1,1 @@</span><a href="#l1.3"></a>
880 <span id="l1.4" class="plusline">+a</span><a href="#l1.4"></a></pre></div></td></tr>
1143 <span id="l1.4" class="plusline">+a</span><a href="#l1.4"></a></pre></div></td></tr>
881
1144
882 </tbody>
1145 </tbody>
883 </table>
1146 </table>
884
1147
885 <div class="navigate">
1148 <div class="navigate">
886 <a href="/log/4/a?patch=1&revcount=30">less</a>
1149 <a href="/log/4/a?patch=1&revcount=30">less</a>
887 <a href="/log/4/a?patch=1&revcount=120">more</a>
1150 <a href="/log/4/a?patch=1&revcount=120">more</a>
888 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
1151 | <a href="/log/5ed941583260/a">(0)</a> <a href="/log/tip/a">tip</a>
889 </div>
1152 </div>
890
1153
891 </div>
1154 </div>
892 </div>
1155 </div>
893
1156
894
1157
895
1158
896 </body>
1159 </body>
897 </html>
1160 </html>
898
1161
899
1162
900 rss log
1163 rss log
901
1164
902 $ (get-with-headers.py localhost:$HGPORT 'rss-log/tip/a')
1165 $ (get-with-headers.py localhost:$HGPORT 'rss-log/tip/a')
903 200 Script output follows
1166 200 Script output follows
904
1167
905 <?xml version="1.0" encoding="ascii"?>
1168 <?xml version="1.0" encoding="ascii"?>
906 <rss version="2.0">
1169 <rss version="2.0">
907 <channel>
1170 <channel>
908 <link>http://*:$HGPORT/</link> (glob)
1171 <link>http://*:$HGPORT/</link> (glob)
909 <language>en-us</language>
1172 <language>en-us</language>
910
1173
911 <title>test: a history</title>
1174 <title>test: a history</title>
912 <description>a revision history</description>
1175 <description>a revision history</description>
913 <item>
1176 <item>
914 <title>second a</title>
1177 <title>second a</title>
915 <link>http://*:$HGPORT/log/3f41bc784e7e/a</link> (glob)
1178 <link>http://*:$HGPORT/log/3f41bc784e7e/a</link> (glob)
916 <description><![CDATA[second a]]></description>
1179 <description><![CDATA[second a]]></description>
917 <author>&#116;&#101;&#115;&#116;</author>
1180 <author>&#116;&#101;&#115;&#116;</author>
918 <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
1181 <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
919 </item>
1182 </item>
920 <item>
1183 <item>
921 <title>first a</title>
1184 <title>first a</title>
922 <link>http://*:$HGPORT/log/5ed941583260/a</link> (glob)
1185 <link>http://*:$HGPORT/log/5ed941583260/a</link> (glob)
923 <description><![CDATA[first a]]></description>
1186 <description><![CDATA[first a]]></description>
924 <author>&#116;&#101;&#115;&#116;</author>
1187 <author>&#116;&#101;&#115;&#116;</author>
925 <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
1188 <pubDate>Thu, 01 Jan 1970 00:00:00 +0000</pubDate>
926 </item>
1189 </item>
927
1190
928 </channel>
1191 </channel>
929 </rss>
1192 </rss>
930
1193
931 atom log
1194 atom log
932
1195
933 $ (get-with-headers.py localhost:$HGPORT 'atom-log/tip/a')
1196 $ (get-with-headers.py localhost:$HGPORT 'atom-log/tip/a')
934 200 Script output follows
1197 200 Script output follows
935
1198
936 <?xml version="1.0" encoding="ascii"?>
1199 <?xml version="1.0" encoding="ascii"?>
937 <feed xmlns="http://www.w3.org/2005/Atom">
1200 <feed xmlns="http://www.w3.org/2005/Atom">
938 <id>http://*:$HGPORT/atom-log/tip/a</id> (glob)
1201 <id>http://*:$HGPORT/atom-log/tip/a</id> (glob)
939 <link rel="self" href="http://*:$HGPORT/atom-log/tip/a"/> (glob)
1202 <link rel="self" href="http://*:$HGPORT/atom-log/tip/a"/> (glob)
940 <title>test: a history</title>
1203 <title>test: a history</title>
941 <updated>1970-01-01T00:00:00+00:00</updated>
1204 <updated>1970-01-01T00:00:00+00:00</updated>
942
1205
943 <entry>
1206 <entry>
944 <title>[a-branch] second a</title>
1207 <title>[a-branch] second a</title>
945 <id>http://*:$HGPORT/#changeset-3f41bc784e7e73035c6d47112c6cc7efb673adf8</id> (glob)
1208 <id>http://*:$HGPORT/#changeset-3f41bc784e7e73035c6d47112c6cc7efb673adf8</id> (glob)
946 <link href="http://*:$HGPORT/rev/3f41bc784e7e"/> (glob)
1209 <link href="http://*:$HGPORT/rev/3f41bc784e7e"/> (glob)
947 <author>
1210 <author>
948 <name>test</name>
1211 <name>test</name>
949 <email>&#116;&#101;&#115;&#116;</email>
1212 <email>&#116;&#101;&#115;&#116;</email>
950 </author>
1213 </author>
951 <updated>1970-01-01T00:00:00+00:00</updated>
1214 <updated>1970-01-01T00:00:00+00:00</updated>
952 <published>1970-01-01T00:00:00+00:00</published>
1215 <published>1970-01-01T00:00:00+00:00</published>
953 <content type="xhtml">
1216 <content type="xhtml">
954 <table xmlns="http://www.w3.org/1999/xhtml">
1217 <table xmlns="http://www.w3.org/1999/xhtml">
955 <tr>
1218 <tr>
956 <th style="text-align:left;">changeset</th>
1219 <th style="text-align:left;">changeset</th>
957 <td>3f41bc784e7e</td>
1220 <td>3f41bc784e7e</td>
958 </tr>
1221 </tr>
959 <tr>
1222 <tr>
960 <th style="text-align:left;">branch</th>
1223 <th style="text-align:left;">branch</th>
961 <td>a-branch</td>
1224 <td>a-branch</td>
962 </tr>
1225 </tr>
963 <tr>
1226 <tr>
964 <th style="text-align:left;">bookmark</th>
1227 <th style="text-align:left;">bookmark</th>
965 <td></td>
1228 <td></td>
966 </tr>
1229 </tr>
967 <tr>
1230 <tr>
968 <th style="text-align:left;">tag</th>
1231 <th style="text-align:left;">tag</th>
969 <td></td>
1232 <td></td>
970 </tr>
1233 </tr>
971 <tr>
1234 <tr>
972 <th style="text-align:left;">user</th>
1235 <th style="text-align:left;">user</th>
973 <td>&#116;&#101;&#115;&#116;</td>
1236 <td>&#116;&#101;&#115;&#116;</td>
974 </tr>
1237 </tr>
975 <tr>
1238 <tr>
976 <th style="text-align:left;vertical-align:top;">description</th>
1239 <th style="text-align:left;vertical-align:top;">description</th>
977 <td>second a</td>
1240 <td>second a</td>
978 </tr>
1241 </tr>
979 <tr>
1242 <tr>
980 <th style="text-align:left;vertical-align:top;">files</th>
1243 <th style="text-align:left;vertical-align:top;">files</th>
981 <td></td>
1244 <td></td>
982 </tr>
1245 </tr>
983 </table>
1246 </table>
984 </content>
1247 </content>
985 </entry>
1248 </entry>
986 <entry>
1249 <entry>
987 <title>first a</title>
1250 <title>first a</title>
988 <id>http://*:$HGPORT/#changeset-5ed941583260248620985524192fdc382ef57c36</id> (glob)
1251 <id>http://*:$HGPORT/#changeset-5ed941583260248620985524192fdc382ef57c36</id> (glob)
989 <link href="http://*:$HGPORT/rev/5ed941583260"/> (glob)
1252 <link href="http://*:$HGPORT/rev/5ed941583260"/> (glob)
990 <author>
1253 <author>
991 <name>test</name>
1254 <name>test</name>
992 <email>&#116;&#101;&#115;&#116;</email>
1255 <email>&#116;&#101;&#115;&#116;</email>
993 </author>
1256 </author>
994 <updated>1970-01-01T00:00:00+00:00</updated>
1257 <updated>1970-01-01T00:00:00+00:00</updated>
995 <published>1970-01-01T00:00:00+00:00</published>
1258 <published>1970-01-01T00:00:00+00:00</published>
996 <content type="xhtml">
1259 <content type="xhtml">
997 <table xmlns="http://www.w3.org/1999/xhtml">
1260 <table xmlns="http://www.w3.org/1999/xhtml">
998 <tr>
1261 <tr>
999 <th style="text-align:left;">changeset</th>
1262 <th style="text-align:left;">changeset</th>
1000 <td>5ed941583260</td>
1263 <td>5ed941583260</td>
1001 </tr>
1264 </tr>
1002 <tr>
1265 <tr>
1003 <th style="text-align:left;">branch</th>
1266 <th style="text-align:left;">branch</th>
1004 <td></td>
1267 <td></td>
1005 </tr>
1268 </tr>
1006 <tr>
1269 <tr>
1007 <th style="text-align:left;">bookmark</th>
1270 <th style="text-align:left;">bookmark</th>
1008 <td>a-bookmark</td>
1271 <td>a-bookmark</td>
1009 </tr>
1272 </tr>
1010 <tr>
1273 <tr>
1011 <th style="text-align:left;">tag</th>
1274 <th style="text-align:left;">tag</th>
1012 <td>a-tag</td>
1275 <td>a-tag</td>
1013 </tr>
1276 </tr>
1014 <tr>
1277 <tr>
1015 <th style="text-align:left;">user</th>
1278 <th style="text-align:left;">user</th>
1016 <td>&#116;&#101;&#115;&#116;</td>
1279 <td>&#116;&#101;&#115;&#116;</td>
1017 </tr>
1280 </tr>
1018 <tr>
1281 <tr>
1019 <th style="text-align:left;vertical-align:top;">description</th>
1282 <th style="text-align:left;vertical-align:top;">description</th>
1020 <td>first a</td>
1283 <td>first a</td>
1021 </tr>
1284 </tr>
1022 <tr>
1285 <tr>
1023 <th style="text-align:left;vertical-align:top;">files</th>
1286 <th style="text-align:left;vertical-align:top;">files</th>
1024 <td></td>
1287 <td></td>
1025 </tr>
1288 </tr>
1026 </table>
1289 </table>
1027 </content>
1290 </content>
1028 </entry>
1291 </entry>
1029
1292
1030 </feed>
1293 </feed>
1031
1294
1032 errors
1295 errors
1033
1296
1034 $ cat errors.log
1297 $ cat errors.log
1035
1298
1036 $ cd ..
1299 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now