##// END OF EJS Templates
hgweb: use revset.spanset where appropriate...
Yuya Nishihara -
r23992:db85e454 default
parent child Browse files
Show More
@@ -1,1091 +1,1091 b''
1 1 #
2 2 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import os, mimetypes, re, cgi, copy
9 9 import webutil
10 10 from mercurial import error, encoding, archival, templater, templatefilters
11 11 from mercurial.node import short, hex
12 12 from mercurial import util
13 13 from common import paritygen, staticfile, get_contact, ErrorResponse
14 14 from common import HTTP_OK, HTTP_FORBIDDEN, HTTP_NOT_FOUND
15 15 from mercurial import graphmod, patch
16 16 from mercurial import help as helpmod
17 17 from mercurial import scmutil
18 18 from mercurial.i18n import _
19 19 from mercurial.error import ParseError, RepoLookupError, Abort
20 20 from mercurial import revset
21 21
22 22 # __all__ is populated with the allowed commands. Be sure to add to it if
23 23 # you're adding a new command, or the new command won't work.
24 24
25 25 __all__ = [
26 26 'log', 'rawfile', 'file', 'changelog', 'shortlog', 'changeset', 'rev',
27 27 'manifest', 'tags', 'bookmarks', 'branches', 'summary', 'filediff', 'diff',
28 28 'comparison', 'annotate', 'filelog', 'archive', 'static', 'graph', 'help',
29 29 ]
30 30
31 31 def log(web, req, tmpl):
32 32 if 'file' in req.form and req.form['file'][0]:
33 33 return filelog(web, req, tmpl)
34 34 else:
35 35 return changelog(web, req, tmpl)
36 36
37 37 def rawfile(web, req, tmpl):
38 38 guessmime = web.configbool('web', 'guessmime', False)
39 39
40 40 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
41 41 if not path:
42 42 content = manifest(web, req, tmpl)
43 43 req.respond(HTTP_OK, web.ctype)
44 44 return content
45 45
46 46 try:
47 47 fctx = webutil.filectx(web.repo, req)
48 48 except error.LookupError, inst:
49 49 try:
50 50 content = manifest(web, req, tmpl)
51 51 req.respond(HTTP_OK, web.ctype)
52 52 return content
53 53 except ErrorResponse:
54 54 raise inst
55 55
56 56 path = fctx.path()
57 57 text = fctx.data()
58 58 mt = 'application/binary'
59 59 if guessmime:
60 60 mt = mimetypes.guess_type(path)[0]
61 61 if mt is None:
62 62 mt = util.binary(text) and 'application/binary' or 'text/plain'
63 63 if mt.startswith('text/'):
64 64 mt += '; charset="%s"' % encoding.encoding
65 65
66 66 req.respond(HTTP_OK, mt, path, body=text)
67 67 return []
68 68
69 69 def _filerevision(web, tmpl, fctx):
70 70 f = fctx.path()
71 71 text = fctx.data()
72 72 parity = paritygen(web.stripecount)
73 73
74 74 if util.binary(text):
75 75 mt = mimetypes.guess_type(f)[0] or 'application/octet-stream'
76 76 text = '(binary:%s)' % mt
77 77
78 78 def lines():
79 79 for lineno, t in enumerate(text.splitlines(True)):
80 80 yield {"line": t,
81 81 "lineid": "l%d" % (lineno + 1),
82 82 "linenumber": "% 6d" % (lineno + 1),
83 83 "parity": parity.next()}
84 84
85 85 return tmpl("filerevision",
86 86 file=f,
87 87 path=webutil.up(f),
88 88 text=lines(),
89 89 rev=fctx.rev(),
90 90 node=fctx.hex(),
91 91 author=fctx.user(),
92 92 date=fctx.date(),
93 93 desc=fctx.description(),
94 94 extra=fctx.extra(),
95 95 branch=webutil.nodebranchnodefault(fctx),
96 96 parent=webutil.parents(fctx),
97 97 child=webutil.children(fctx),
98 98 rename=webutil.renamelink(fctx),
99 99 permissions=fctx.manifest().flags(f))
100 100
101 101 def file(web, req, tmpl):
102 102 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
103 103 if not path:
104 104 return manifest(web, req, tmpl)
105 105 try:
106 106 return _filerevision(web, tmpl, webutil.filectx(web.repo, req))
107 107 except error.LookupError, inst:
108 108 try:
109 109 return manifest(web, req, tmpl)
110 110 except ErrorResponse:
111 111 raise inst
112 112
113 113 def _search(web, req, tmpl):
114 114 MODE_REVISION = 'rev'
115 115 MODE_KEYWORD = 'keyword'
116 116 MODE_REVSET = 'revset'
117 117
118 118 def revsearch(ctx):
119 119 yield ctx
120 120
121 121 def keywordsearch(query):
122 122 lower = encoding.lower
123 123 qw = lower(query).split()
124 124
125 125 def revgen():
126 126 cl = web.repo.changelog
127 127 for i in xrange(len(web.repo) - 1, 0, -100):
128 128 l = []
129 129 for j in cl.revs(max(0, i - 99), i):
130 130 ctx = web.repo[j]
131 131 l.append(ctx)
132 132 l.reverse()
133 133 for e in l:
134 134 yield e
135 135
136 136 for ctx in revgen():
137 137 miss = 0
138 138 for q in qw:
139 139 if not (q in lower(ctx.user()) or
140 140 q in lower(ctx.description()) or
141 141 q in lower(" ".join(ctx.files()))):
142 142 miss = 1
143 143 break
144 144 if miss:
145 145 continue
146 146
147 147 yield ctx
148 148
149 149 def revsetsearch(revs):
150 150 for r in revs:
151 151 yield web.repo[r]
152 152
153 153 searchfuncs = {
154 154 MODE_REVISION: (revsearch, 'exact revision search'),
155 155 MODE_KEYWORD: (keywordsearch, 'literal keyword search'),
156 156 MODE_REVSET: (revsetsearch, 'revset expression search'),
157 157 }
158 158
159 159 def getsearchmode(query):
160 160 try:
161 161 ctx = web.repo[query]
162 162 except (error.RepoError, error.LookupError):
163 163 # query is not an exact revision pointer, need to
164 164 # decide if it's a revset expression or keywords
165 165 pass
166 166 else:
167 167 return MODE_REVISION, ctx
168 168
169 169 revdef = 'reverse(%s)' % query
170 170 try:
171 171 tree, pos = revset.parse(revdef)
172 172 except ParseError:
173 173 # can't parse to a revset tree
174 174 return MODE_KEYWORD, query
175 175
176 176 if revset.depth(tree) <= 2:
177 177 # no revset syntax used
178 178 return MODE_KEYWORD, query
179 179
180 180 if util.any((token, (value or '')[:3]) == ('string', 're:')
181 181 for token, value, pos in revset.tokenize(revdef)):
182 182 return MODE_KEYWORD, query
183 183
184 184 funcsused = revset.funcsused(tree)
185 185 if not funcsused.issubset(revset.safesymbols):
186 186 return MODE_KEYWORD, query
187 187
188 188 mfunc = revset.match(web.repo.ui, revdef)
189 189 try:
190 revs = mfunc(web.repo, revset.baseset(web.repo))
190 revs = mfunc(web.repo, revset.spanset(web.repo))
191 191 return MODE_REVSET, revs
192 192 # ParseError: wrongly placed tokens, wrongs arguments, etc
193 193 # RepoLookupError: no such revision, e.g. in 'revision:'
194 194 # Abort: bookmark/tag not exists
195 195 # LookupError: ambiguous identifier, e.g. in '(bc)' on a large repo
196 196 except (ParseError, RepoLookupError, Abort, LookupError):
197 197 return MODE_KEYWORD, query
198 198
199 199 def changelist(**map):
200 200 count = 0
201 201
202 202 for ctx in searchfunc[0](funcarg):
203 203 count += 1
204 204 n = ctx.node()
205 205 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
206 206 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
207 207
208 208 yield tmpl('searchentry',
209 209 parity=parity.next(),
210 210 author=ctx.user(),
211 211 parent=webutil.parents(ctx),
212 212 child=webutil.children(ctx),
213 213 changelogtag=showtags,
214 214 desc=ctx.description(),
215 215 extra=ctx.extra(),
216 216 date=ctx.date(),
217 217 files=files,
218 218 rev=ctx.rev(),
219 219 node=hex(n),
220 220 tags=webutil.nodetagsdict(web.repo, n),
221 221 bookmarks=webutil.nodebookmarksdict(web.repo, n),
222 222 inbranch=webutil.nodeinbranch(web.repo, ctx),
223 223 branches=webutil.nodebranchdict(web.repo, ctx))
224 224
225 225 if count >= revcount:
226 226 break
227 227
228 228 query = req.form['rev'][0]
229 229 revcount = web.maxchanges
230 230 if 'revcount' in req.form:
231 231 try:
232 232 revcount = int(req.form.get('revcount', [revcount])[0])
233 233 revcount = max(revcount, 1)
234 234 tmpl.defaults['sessionvars']['revcount'] = revcount
235 235 except ValueError:
236 236 pass
237 237
238 238 lessvars = copy.copy(tmpl.defaults['sessionvars'])
239 239 lessvars['revcount'] = max(revcount / 2, 1)
240 240 lessvars['rev'] = query
241 241 morevars = copy.copy(tmpl.defaults['sessionvars'])
242 242 morevars['revcount'] = revcount * 2
243 243 morevars['rev'] = query
244 244
245 245 mode, funcarg = getsearchmode(query)
246 246
247 247 if 'forcekw' in req.form:
248 248 showforcekw = ''
249 249 showunforcekw = searchfuncs[mode][1]
250 250 mode = MODE_KEYWORD
251 251 funcarg = query
252 252 else:
253 253 if mode != MODE_KEYWORD:
254 254 showforcekw = searchfuncs[MODE_KEYWORD][1]
255 255 else:
256 256 showforcekw = ''
257 257 showunforcekw = ''
258 258
259 259 searchfunc = searchfuncs[mode]
260 260
261 261 tip = web.repo['tip']
262 262 parity = paritygen(web.stripecount)
263 263
264 264 return tmpl('search', query=query, node=tip.hex(),
265 265 entries=changelist, archives=web.archivelist("tip"),
266 266 morevars=morevars, lessvars=lessvars,
267 267 modedesc=searchfunc[1],
268 268 showforcekw=showforcekw, showunforcekw=showunforcekw)
269 269
270 270 def changelog(web, req, tmpl, shortlog=False):
271 271
272 272 query = ''
273 273 if 'node' in req.form:
274 274 ctx = webutil.changectx(web.repo, req)
275 275 elif 'rev' in req.form:
276 276 return _search(web, req, tmpl)
277 277 else:
278 278 ctx = web.repo['tip']
279 279
280 280 def changelist():
281 281 revs = []
282 282 if pos != -1:
283 283 revs = web.repo.changelog.revs(pos, 0)
284 284 curcount = 0
285 285 for rev in revs:
286 286 curcount += 1
287 287 if curcount > revcount + 1:
288 288 break
289 289
290 290 entry = webutil.changelistentry(web, web.repo[rev], tmpl)
291 291 entry['parity'] = parity.next()
292 292 yield entry
293 293
294 294 revcount = shortlog and web.maxshortchanges or web.maxchanges
295 295 if 'revcount' in req.form:
296 296 try:
297 297 revcount = int(req.form.get('revcount', [revcount])[0])
298 298 revcount = max(revcount, 1)
299 299 tmpl.defaults['sessionvars']['revcount'] = revcount
300 300 except ValueError:
301 301 pass
302 302
303 303 lessvars = copy.copy(tmpl.defaults['sessionvars'])
304 304 lessvars['revcount'] = max(revcount / 2, 1)
305 305 morevars = copy.copy(tmpl.defaults['sessionvars'])
306 306 morevars['revcount'] = revcount * 2
307 307
308 308 count = len(web.repo)
309 309 pos = ctx.rev()
310 310 parity = paritygen(web.stripecount)
311 311
312 312 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
313 313
314 314 entries = list(changelist())
315 315 latestentry = entries[:1]
316 316 if len(entries) > revcount:
317 317 nextentry = entries[-1:]
318 318 entries = entries[:-1]
319 319 else:
320 320 nextentry = []
321 321
322 322 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
323 323 node=ctx.hex(), rev=pos, changesets=count,
324 324 entries=entries,
325 325 latestentry=latestentry, nextentry=nextentry,
326 326 archives=web.archivelist("tip"), revcount=revcount,
327 327 morevars=morevars, lessvars=lessvars, query=query)
328 328
329 329 def shortlog(web, req, tmpl):
330 330 return changelog(web, req, tmpl, shortlog=True)
331 331
332 332 def changeset(web, req, tmpl):
333 333 ctx = webutil.changectx(web.repo, req)
334 334 basectx = webutil.basechangectx(web.repo, req)
335 335 if basectx is None:
336 336 basectx = ctx.p1()
337 337 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
338 338 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
339 339 ctx.node())
340 340 showbranch = webutil.nodebranchnodefault(ctx)
341 341
342 342 files = []
343 343 parity = paritygen(web.stripecount)
344 344 for blockno, f in enumerate(ctx.files()):
345 345 template = f in ctx and 'filenodelink' or 'filenolink'
346 346 files.append(tmpl(template,
347 347 node=ctx.hex(), file=f, blockno=blockno + 1,
348 348 parity=parity.next()))
349 349
350 350 style = web.config('web', 'style', 'paper')
351 351 if 'style' in req.form:
352 352 style = req.form['style'][0]
353 353
354 354 parity = paritygen(web.stripecount)
355 355 diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
356 356
357 357 parity = paritygen(web.stripecount)
358 358 diffstatgen = webutil.diffstatgen(ctx, basectx)
359 359 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
360 360
361 361 return tmpl('changeset',
362 362 diff=diffs,
363 363 rev=ctx.rev(),
364 364 node=ctx.hex(),
365 365 parent=tuple(webutil.parents(ctx)),
366 366 child=webutil.children(ctx),
367 367 basenode=basectx.hex(),
368 368 changesettag=showtags,
369 369 changesetbookmark=showbookmarks,
370 370 changesetbranch=showbranch,
371 371 author=ctx.user(),
372 372 desc=ctx.description(),
373 373 extra=ctx.extra(),
374 374 date=ctx.date(),
375 375 files=files,
376 376 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
377 377 diffstat=diffstat,
378 378 archives=web.archivelist(ctx.hex()),
379 379 tags=webutil.nodetagsdict(web.repo, ctx.node()),
380 380 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
381 381 branch=webutil.nodebranchnodefault(ctx),
382 382 inbranch=webutil.nodeinbranch(web.repo, ctx),
383 383 branches=webutil.nodebranchdict(web.repo, ctx))
384 384
385 385 rev = changeset
386 386
387 387 def decodepath(path):
388 388 """Hook for mapping a path in the repository to a path in the
389 389 working copy.
390 390
391 391 Extensions (e.g., largefiles) can override this to remap files in
392 392 the virtual file system presented by the manifest command below."""
393 393 return path
394 394
395 395 def manifest(web, req, tmpl):
396 396 ctx = webutil.changectx(web.repo, req)
397 397 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
398 398 mf = ctx.manifest()
399 399 node = ctx.node()
400 400
401 401 files = {}
402 402 dirs = {}
403 403 parity = paritygen(web.stripecount)
404 404
405 405 if path and path[-1] != "/":
406 406 path += "/"
407 407 l = len(path)
408 408 abspath = "/" + path
409 409
410 410 for full, n in mf.iteritems():
411 411 # the virtual path (working copy path) used for the full
412 412 # (repository) path
413 413 f = decodepath(full)
414 414
415 415 if f[:l] != path:
416 416 continue
417 417 remain = f[l:]
418 418 elements = remain.split('/')
419 419 if len(elements) == 1:
420 420 files[remain] = full
421 421 else:
422 422 h = dirs # need to retain ref to dirs (root)
423 423 for elem in elements[0:-1]:
424 424 if elem not in h:
425 425 h[elem] = {}
426 426 h = h[elem]
427 427 if len(h) > 1:
428 428 break
429 429 h[None] = None # denotes files present
430 430
431 431 if mf and not files and not dirs:
432 432 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
433 433
434 434 def filelist(**map):
435 435 for f in sorted(files):
436 436 full = files[f]
437 437
438 438 fctx = ctx.filectx(full)
439 439 yield {"file": full,
440 440 "parity": parity.next(),
441 441 "basename": f,
442 442 "date": fctx.date(),
443 443 "size": fctx.size(),
444 444 "permissions": mf.flags(full)}
445 445
446 446 def dirlist(**map):
447 447 for d in sorted(dirs):
448 448
449 449 emptydirs = []
450 450 h = dirs[d]
451 451 while isinstance(h, dict) and len(h) == 1:
452 452 k, v = h.items()[0]
453 453 if v:
454 454 emptydirs.append(k)
455 455 h = v
456 456
457 457 path = "%s%s" % (abspath, d)
458 458 yield {"parity": parity.next(),
459 459 "path": path,
460 460 "emptydirs": "/".join(emptydirs),
461 461 "basename": d}
462 462
463 463 return tmpl("manifest",
464 464 rev=ctx.rev(),
465 465 node=hex(node),
466 466 path=abspath,
467 467 up=webutil.up(abspath),
468 468 upparity=parity.next(),
469 469 fentries=filelist,
470 470 dentries=dirlist,
471 471 archives=web.archivelist(hex(node)),
472 472 tags=webutil.nodetagsdict(web.repo, node),
473 473 bookmarks=webutil.nodebookmarksdict(web.repo, node),
474 474 inbranch=webutil.nodeinbranch(web.repo, ctx),
475 475 branches=webutil.nodebranchdict(web.repo, ctx))
476 476
477 477 def tags(web, req, tmpl):
478 478 i = list(reversed(web.repo.tagslist()))
479 479 parity = paritygen(web.stripecount)
480 480
481 481 def entries(notip, latestonly, **map):
482 482 t = i
483 483 if notip:
484 484 t = [(k, n) for k, n in i if k != "tip"]
485 485 if latestonly:
486 486 t = t[:1]
487 487 for k, n in t:
488 488 yield {"parity": parity.next(),
489 489 "tag": k,
490 490 "date": web.repo[n].date(),
491 491 "node": hex(n)}
492 492
493 493 return tmpl("tags",
494 494 node=hex(web.repo.changelog.tip()),
495 495 entries=lambda **x: entries(False, False, **x),
496 496 entriesnotip=lambda **x: entries(True, False, **x),
497 497 latestentry=lambda **x: entries(True, True, **x))
498 498
499 499 def bookmarks(web, req, tmpl):
500 500 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
501 501 parity = paritygen(web.stripecount)
502 502
503 503 def entries(latestonly, **map):
504 504 if latestonly:
505 505 t = [min(i)]
506 506 else:
507 507 t = sorted(i)
508 508 for k, n in t:
509 509 yield {"parity": parity.next(),
510 510 "bookmark": k,
511 511 "date": web.repo[n].date(),
512 512 "node": hex(n)}
513 513
514 514 return tmpl("bookmarks",
515 515 node=hex(web.repo.changelog.tip()),
516 516 entries=lambda **x: entries(latestonly=False, **x),
517 517 latestentry=lambda **x: entries(latestonly=True, **x))
518 518
519 519 def branches(web, req, tmpl):
520 520 tips = []
521 521 heads = web.repo.heads()
522 522 parity = paritygen(web.stripecount)
523 523 sortkey = lambda item: (not item[1], item[0].rev())
524 524
525 525 def entries(limit, **map):
526 526 count = 0
527 527 if not tips:
528 528 for tag, hs, tip, closed in web.repo.branchmap().iterbranches():
529 529 tips.append((web.repo[tip], closed))
530 530 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
531 531 if limit > 0 and count >= limit:
532 532 return
533 533 count += 1
534 534 if closed:
535 535 status = 'closed'
536 536 elif ctx.node() not in heads:
537 537 status = 'inactive'
538 538 else:
539 539 status = 'open'
540 540 yield {'parity': parity.next(),
541 541 'branch': ctx.branch(),
542 542 'status': status,
543 543 'node': ctx.hex(),
544 544 'date': ctx.date()}
545 545
546 546 return tmpl('branches', node=hex(web.repo.changelog.tip()),
547 547 entries=lambda **x: entries(0, **x),
548 548 latestentry=lambda **x: entries(1, **x))
549 549
550 550 def summary(web, req, tmpl):
551 551 i = reversed(web.repo.tagslist())
552 552
553 553 def tagentries(**map):
554 554 parity = paritygen(web.stripecount)
555 555 count = 0
556 556 for k, n in i:
557 557 if k == "tip": # skip tip
558 558 continue
559 559
560 560 count += 1
561 561 if count > 10: # limit to 10 tags
562 562 break
563 563
564 564 yield tmpl("tagentry",
565 565 parity=parity.next(),
566 566 tag=k,
567 567 node=hex(n),
568 568 date=web.repo[n].date())
569 569
570 570 def bookmarks(**map):
571 571 parity = paritygen(web.stripecount)
572 572 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
573 573 for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
574 574 yield {'parity': parity.next(),
575 575 'bookmark': k,
576 576 'date': web.repo[n].date(),
577 577 'node': hex(n)}
578 578
579 579 def branches(**map):
580 580 parity = paritygen(web.stripecount)
581 581
582 582 b = web.repo.branchmap()
583 583 l = [(-web.repo.changelog.rev(tip), tip, tag)
584 584 for tag, heads, tip, closed in b.iterbranches()]
585 585 for r, n, t in sorted(l):
586 586 yield {'parity': parity.next(),
587 587 'branch': t,
588 588 'node': hex(n),
589 589 'date': web.repo[n].date()}
590 590
591 591 def changelist(**map):
592 592 parity = paritygen(web.stripecount, offset=start - end)
593 593 l = [] # build a list in forward order for efficiency
594 594 revs = []
595 595 if start < end:
596 596 revs = web.repo.changelog.revs(start, end - 1)
597 597 for i in revs:
598 598 ctx = web.repo[i]
599 599 n = ctx.node()
600 600 hn = hex(n)
601 601
602 602 l.append(tmpl(
603 603 'shortlogentry',
604 604 parity=parity.next(),
605 605 author=ctx.user(),
606 606 desc=ctx.description(),
607 607 extra=ctx.extra(),
608 608 date=ctx.date(),
609 609 rev=i,
610 610 node=hn,
611 611 tags=webutil.nodetagsdict(web.repo, n),
612 612 bookmarks=webutil.nodebookmarksdict(web.repo, n),
613 613 inbranch=webutil.nodeinbranch(web.repo, ctx),
614 614 branches=webutil.nodebranchdict(web.repo, ctx)))
615 615
616 616 l.reverse()
617 617 yield l
618 618
619 619 tip = web.repo['tip']
620 620 count = len(web.repo)
621 621 start = max(0, count - web.maxchanges)
622 622 end = min(count, start + web.maxchanges)
623 623
624 624 return tmpl("summary",
625 625 desc=web.config("web", "description", "unknown"),
626 626 owner=get_contact(web.config) or "unknown",
627 627 lastchange=tip.date(),
628 628 tags=tagentries,
629 629 bookmarks=bookmarks,
630 630 branches=branches,
631 631 shortlog=changelist,
632 632 node=tip.hex(),
633 633 archives=web.archivelist("tip"))
634 634
635 635 def filediff(web, req, tmpl):
636 636 fctx, ctx = None, None
637 637 try:
638 638 fctx = webutil.filectx(web.repo, req)
639 639 except LookupError:
640 640 ctx = webutil.changectx(web.repo, req)
641 641 path = webutil.cleanpath(web.repo, req.form['file'][0])
642 642 if path not in ctx.files():
643 643 raise
644 644
645 645 if fctx is not None:
646 646 n = fctx.node()
647 647 path = fctx.path()
648 648 ctx = fctx.changectx()
649 649 else:
650 650 n = ctx.node()
651 651 # path already defined in except clause
652 652
653 653 parity = paritygen(web.stripecount)
654 654 style = web.config('web', 'style', 'paper')
655 655 if 'style' in req.form:
656 656 style = req.form['style'][0]
657 657
658 658 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
659 659 rename = fctx and webutil.renamelink(fctx) or []
660 660 ctx = fctx and fctx or ctx
661 661 return tmpl("filediff",
662 662 file=path,
663 663 node=hex(n),
664 664 rev=ctx.rev(),
665 665 date=ctx.date(),
666 666 desc=ctx.description(),
667 667 extra=ctx.extra(),
668 668 author=ctx.user(),
669 669 rename=rename,
670 670 branch=webutil.nodebranchnodefault(ctx),
671 671 parent=webutil.parents(ctx),
672 672 child=webutil.children(ctx),
673 673 diff=diffs)
674 674
675 675 diff = filediff
676 676
677 677 def comparison(web, req, tmpl):
678 678 ctx = webutil.changectx(web.repo, req)
679 679 if 'file' not in req.form:
680 680 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
681 681 path = webutil.cleanpath(web.repo, req.form['file'][0])
682 682 rename = path in ctx and webutil.renamelink(ctx[path]) or []
683 683
684 684 parsecontext = lambda v: v == 'full' and -1 or int(v)
685 685 if 'context' in req.form:
686 686 context = parsecontext(req.form['context'][0])
687 687 else:
688 688 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
689 689
690 690 def filelines(f):
691 691 if util.binary(f.data()):
692 692 mt = mimetypes.guess_type(f.path())[0]
693 693 if not mt:
694 694 mt = 'application/octet-stream'
695 695 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
696 696 return f.data().splitlines()
697 697
698 698 parent = ctx.p1()
699 699 leftrev = parent.rev()
700 700 leftnode = parent.node()
701 701 rightrev = ctx.rev()
702 702 rightnode = ctx.node()
703 703 if path in ctx:
704 704 fctx = ctx[path]
705 705 rightlines = filelines(fctx)
706 706 if path not in parent:
707 707 leftlines = ()
708 708 else:
709 709 pfctx = parent[path]
710 710 leftlines = filelines(pfctx)
711 711 else:
712 712 rightlines = ()
713 713 fctx = ctx.parents()[0][path]
714 714 leftlines = filelines(fctx)
715 715
716 716 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
717 717 return tmpl('filecomparison',
718 718 file=path,
719 719 node=hex(ctx.node()),
720 720 rev=ctx.rev(),
721 721 date=ctx.date(),
722 722 desc=ctx.description(),
723 723 extra=ctx.extra(),
724 724 author=ctx.user(),
725 725 rename=rename,
726 726 branch=webutil.nodebranchnodefault(ctx),
727 727 parent=webutil.parents(fctx),
728 728 child=webutil.children(fctx),
729 729 leftrev=leftrev,
730 730 leftnode=hex(leftnode),
731 731 rightrev=rightrev,
732 732 rightnode=hex(rightnode),
733 733 comparison=comparison)
734 734
735 735 def annotate(web, req, tmpl):
736 736 fctx = webutil.filectx(web.repo, req)
737 737 f = fctx.path()
738 738 parity = paritygen(web.stripecount)
739 739 diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
740 740 section='annotate', whitespace=True)
741 741
742 742 def annotate(**map):
743 743 last = None
744 744 if util.binary(fctx.data()):
745 745 mt = (mimetypes.guess_type(fctx.path())[0]
746 746 or 'application/octet-stream')
747 747 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
748 748 '(binary:%s)' % mt)])
749 749 else:
750 750 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
751 751 diffopts=diffopts))
752 752 for lineno, ((f, targetline), l) in lines:
753 753 fnode = f.filenode()
754 754
755 755 if last != fnode:
756 756 last = fnode
757 757
758 758 yield {"parity": parity.next(),
759 759 "node": f.hex(),
760 760 "rev": f.rev(),
761 761 "author": f.user(),
762 762 "desc": f.description(),
763 763 "extra": f.extra(),
764 764 "file": f.path(),
765 765 "targetline": targetline,
766 766 "line": l,
767 767 "lineid": "l%d" % (lineno + 1),
768 768 "linenumber": "% 6d" % (lineno + 1),
769 769 "revdate": f.date()}
770 770
771 771 return tmpl("fileannotate",
772 772 file=f,
773 773 annotate=annotate,
774 774 path=webutil.up(f),
775 775 rev=fctx.rev(),
776 776 node=fctx.hex(),
777 777 author=fctx.user(),
778 778 date=fctx.date(),
779 779 desc=fctx.description(),
780 780 extra=fctx.extra(),
781 781 rename=webutil.renamelink(fctx),
782 782 branch=webutil.nodebranchnodefault(fctx),
783 783 parent=webutil.parents(fctx),
784 784 child=webutil.children(fctx),
785 785 permissions=fctx.manifest().flags(f))
786 786
787 787 def filelog(web, req, tmpl):
788 788
789 789 try:
790 790 fctx = webutil.filectx(web.repo, req)
791 791 f = fctx.path()
792 792 fl = fctx.filelog()
793 793 except error.LookupError:
794 794 f = webutil.cleanpath(web.repo, req.form['file'][0])
795 795 fl = web.repo.file(f)
796 796 numrevs = len(fl)
797 797 if not numrevs: # file doesn't exist at all
798 798 raise
799 799 rev = webutil.changectx(web.repo, req).rev()
800 800 first = fl.linkrev(0)
801 801 if rev < first: # current rev is from before file existed
802 802 raise
803 803 frev = numrevs - 1
804 804 while fl.linkrev(frev) > rev:
805 805 frev -= 1
806 806 fctx = web.repo.filectx(f, fl.linkrev(frev))
807 807
808 808 revcount = web.maxshortchanges
809 809 if 'revcount' in req.form:
810 810 try:
811 811 revcount = int(req.form.get('revcount', [revcount])[0])
812 812 revcount = max(revcount, 1)
813 813 tmpl.defaults['sessionvars']['revcount'] = revcount
814 814 except ValueError:
815 815 pass
816 816
817 817 lessvars = copy.copy(tmpl.defaults['sessionvars'])
818 818 lessvars['revcount'] = max(revcount / 2, 1)
819 819 morevars = copy.copy(tmpl.defaults['sessionvars'])
820 820 morevars['revcount'] = revcount * 2
821 821
822 822 count = fctx.filerev() + 1
823 823 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
824 824 end = min(count, start + revcount) # last rev on this page
825 825 parity = paritygen(web.stripecount, offset=start - end)
826 826
827 827 def entries():
828 828 l = []
829 829
830 830 repo = web.repo
831 831 revs = fctx.filelog().revs(start, end - 1)
832 832 for i in revs:
833 833 iterfctx = fctx.filectx(i)
834 834
835 835 l.append({"parity": parity.next(),
836 836 "filerev": i,
837 837 "file": f,
838 838 "node": iterfctx.hex(),
839 839 "author": iterfctx.user(),
840 840 "date": iterfctx.date(),
841 841 "rename": webutil.renamelink(iterfctx),
842 842 "parent": webutil.parents(iterfctx),
843 843 "child": webutil.children(iterfctx),
844 844 "desc": iterfctx.description(),
845 845 "extra": iterfctx.extra(),
846 846 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
847 847 "bookmarks": webutil.nodebookmarksdict(
848 848 repo, iterfctx.node()),
849 849 "branch": webutil.nodebranchnodefault(iterfctx),
850 850 "inbranch": webutil.nodeinbranch(repo, iterfctx),
851 851 "branches": webutil.nodebranchdict(repo, iterfctx)})
852 852 for e in reversed(l):
853 853 yield e
854 854
855 855 entries = list(entries())
856 856 latestentry = entries[:1]
857 857
858 858 revnav = webutil.filerevnav(web.repo, fctx.path())
859 859 nav = revnav.gen(end - 1, revcount, count)
860 860 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
861 861 entries=entries,
862 862 latestentry=latestentry,
863 863 revcount=revcount, morevars=morevars, lessvars=lessvars)
864 864
865 865 def archive(web, req, tmpl):
866 866 type_ = req.form.get('type', [None])[0]
867 867 allowed = web.configlist("web", "allow_archive")
868 868 key = req.form['node'][0]
869 869
870 870 if type_ not in web.archives:
871 871 msg = 'Unsupported archive type: %s' % type_
872 872 raise ErrorResponse(HTTP_NOT_FOUND, msg)
873 873
874 874 if not ((type_ in allowed or
875 875 web.configbool("web", "allow" + type_, False))):
876 876 msg = 'Archive type not allowed: %s' % type_
877 877 raise ErrorResponse(HTTP_FORBIDDEN, msg)
878 878
879 879 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
880 880 cnode = web.repo.lookup(key)
881 881 arch_version = key
882 882 if cnode == key or key == 'tip':
883 883 arch_version = short(cnode)
884 884 name = "%s-%s" % (reponame, arch_version)
885 885
886 886 ctx = webutil.changectx(web.repo, req)
887 887 pats = []
888 888 matchfn = scmutil.match(ctx, [])
889 889 file = req.form.get('file', None)
890 890 if file:
891 891 pats = ['path:' + file[0]]
892 892 matchfn = scmutil.match(ctx, pats, default='path')
893 893 if pats:
894 894 files = [f for f in ctx.manifest().keys() if matchfn(f)]
895 895 if not files:
896 896 raise ErrorResponse(HTTP_NOT_FOUND,
897 897 'file(s) not found: %s' % file[0])
898 898
899 899 mimetype, artype, extension, encoding = web.archive_specs[type_]
900 900 headers = [
901 901 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
902 902 ]
903 903 if encoding:
904 904 headers.append(('Content-Encoding', encoding))
905 905 req.headers.extend(headers)
906 906 req.respond(HTTP_OK, mimetype)
907 907
908 908 archival.archive(web.repo, req, cnode, artype, prefix=name,
909 909 matchfn=matchfn,
910 910 subrepos=web.configbool("web", "archivesubrepos"))
911 911 return []
912 912
913 913
914 914 def static(web, req, tmpl):
915 915 fname = req.form['file'][0]
916 916 # a repo owner may set web.static in .hg/hgrc to get any file
917 917 # readable by the user running the CGI script
918 918 static = web.config("web", "static", None, untrusted=False)
919 919 if not static:
920 920 tp = web.templatepath or templater.templatepaths()
921 921 if isinstance(tp, str):
922 922 tp = [tp]
923 923 static = [os.path.join(p, 'static') for p in tp]
924 924 staticfile(static, fname, req)
925 925 return []
926 926
927 927 def graph(web, req, tmpl):
928 928
929 929 ctx = webutil.changectx(web.repo, req)
930 930 rev = ctx.rev()
931 931
932 932 bg_height = 39
933 933 revcount = web.maxshortchanges
934 934 if 'revcount' in req.form:
935 935 try:
936 936 revcount = int(req.form.get('revcount', [revcount])[0])
937 937 revcount = max(revcount, 1)
938 938 tmpl.defaults['sessionvars']['revcount'] = revcount
939 939 except ValueError:
940 940 pass
941 941
942 942 lessvars = copy.copy(tmpl.defaults['sessionvars'])
943 943 lessvars['revcount'] = max(revcount / 2, 1)
944 944 morevars = copy.copy(tmpl.defaults['sessionvars'])
945 945 morevars['revcount'] = revcount * 2
946 946
947 947 count = len(web.repo)
948 948 pos = rev
949 949
950 950 uprev = min(max(0, count - 1), rev + revcount)
951 951 downrev = max(0, rev - revcount)
952 952 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
953 953
954 954 tree = []
955 955 if pos != -1:
956 956 allrevs = web.repo.changelog.revs(pos, 0)
957 957 revs = []
958 958 for i in allrevs:
959 959 revs.append(i)
960 960 if len(revs) >= revcount:
961 961 break
962 962
963 963 # We have to feed a baseset to dagwalker as it is expecting smartset
964 964 # object. This does not have a big impact on hgweb performance itself
965 965 # since hgweb graphing code is not itself lazy yet.
966 966 dag = graphmod.dagwalker(web.repo, revset.baseset(revs))
967 967 # As we said one line above... not lazy.
968 968 tree = list(graphmod.colored(dag, web.repo))
969 969
970 970 def getcolumns(tree):
971 971 cols = 0
972 972 for (id, type, ctx, vtx, edges) in tree:
973 973 if type != graphmod.CHANGESET:
974 974 continue
975 975 cols = max(cols, max([edge[0] for edge in edges] or [0]),
976 976 max([edge[1] for edge in edges] or [0]))
977 977 return cols
978 978
979 979 def graphdata(usetuples, **map):
980 980 data = []
981 981
982 982 row = 0
983 983 for (id, type, ctx, vtx, edges) in tree:
984 984 if type != graphmod.CHANGESET:
985 985 continue
986 986 node = str(ctx)
987 987 age = templatefilters.age(ctx.date())
988 988 desc = templatefilters.firstline(ctx.description())
989 989 desc = cgi.escape(templatefilters.nonempty(desc))
990 990 user = cgi.escape(templatefilters.person(ctx.user()))
991 991 branch = cgi.escape(ctx.branch())
992 992 try:
993 993 branchnode = web.repo.branchtip(branch)
994 994 except error.RepoLookupError:
995 995 branchnode = None
996 996 branch = branch, branchnode == ctx.node()
997 997
998 998 if usetuples:
999 999 data.append((node, vtx, edges, desc, user, age, branch,
1000 1000 [cgi.escape(x) for x in ctx.tags()],
1001 1001 [cgi.escape(x) for x in ctx.bookmarks()]))
1002 1002 else:
1003 1003 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1004 1004 'color': (edge[2] - 1) % 6 + 1,
1005 1005 'width': edge[3], 'bcolor': edge[4]}
1006 1006 for edge in edges]
1007 1007
1008 1008 data.append(
1009 1009 {'node': node,
1010 1010 'col': vtx[0],
1011 1011 'color': (vtx[1] - 1) % 6 + 1,
1012 1012 'edges': edgedata,
1013 1013 'row': row,
1014 1014 'nextrow': row + 1,
1015 1015 'desc': desc,
1016 1016 'user': user,
1017 1017 'age': age,
1018 1018 'bookmarks': webutil.nodebookmarksdict(
1019 1019 web.repo, ctx.node()),
1020 1020 'branches': webutil.nodebranchdict(web.repo, ctx),
1021 1021 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1022 1022 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1023 1023
1024 1024 row += 1
1025 1025
1026 1026 return data
1027 1027
1028 1028 cols = getcolumns(tree)
1029 1029 rows = len(tree)
1030 1030 canvasheight = (rows + 1) * bg_height - 27
1031 1031
1032 1032 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
1033 1033 lessvars=lessvars, morevars=morevars, downrev=downrev,
1034 1034 cols=cols, rows=rows,
1035 1035 canvaswidth=(cols + 1) * bg_height,
1036 1036 truecanvasheight=rows * bg_height,
1037 1037 canvasheight=canvasheight, bg_height=bg_height,
1038 1038 jsdata=lambda **x: graphdata(True, **x),
1039 1039 nodes=lambda **x: graphdata(False, **x),
1040 1040 node=ctx.hex(), changenav=changenav)
1041 1041
1042 1042 def _getdoc(e):
1043 1043 doc = e[0].__doc__
1044 1044 if doc:
1045 1045 doc = _(doc).split('\n')[0]
1046 1046 else:
1047 1047 doc = _('(no help text available)')
1048 1048 return doc
1049 1049
1050 1050 def help(web, req, tmpl):
1051 1051 from mercurial import commands # avoid cycle
1052 1052
1053 1053 topicname = req.form.get('node', [None])[0]
1054 1054 if not topicname:
1055 1055 def topics(**map):
1056 1056 for entries, summary, _doc in helpmod.helptable:
1057 1057 yield {'topic': entries[0], 'summary': summary}
1058 1058
1059 1059 early, other = [], []
1060 1060 primary = lambda s: s.split('|')[0]
1061 1061 for c, e in commands.table.iteritems():
1062 1062 doc = _getdoc(e)
1063 1063 if 'DEPRECATED' in doc or c.startswith('debug'):
1064 1064 continue
1065 1065 cmd = primary(c)
1066 1066 if cmd.startswith('^'):
1067 1067 early.append((cmd[1:], doc))
1068 1068 else:
1069 1069 other.append((cmd, doc))
1070 1070
1071 1071 early.sort()
1072 1072 other.sort()
1073 1073
1074 1074 def earlycommands(**map):
1075 1075 for c, doc in early:
1076 1076 yield {'topic': c, 'summary': doc}
1077 1077
1078 1078 def othercommands(**map):
1079 1079 for c, doc in other:
1080 1080 yield {'topic': c, 'summary': doc}
1081 1081
1082 1082 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1083 1083 othercommands=othercommands, title='Index')
1084 1084
1085 1085 u = webutil.wsgiui()
1086 1086 u.verbose = True
1087 1087 try:
1088 1088 doc = helpmod.help_(u, topicname)
1089 1089 except error.UnknownCommand:
1090 1090 raise ErrorResponse(HTTP_NOT_FOUND)
1091 1091 return tmpl('help', topic=topicname, doc=doc)
General Comments 0
You need to be logged in to leave comments. Login now