##// END OF EJS Templates
hgweb: simplify branches with repo.branchmap().iterbranches()
Brodie Rao -
r20194:9985e188 default
parent child Browse files
Show More
@@ -1,1109 +1,1109 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, nullid
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 190 revs = mfunc(web.repo, list(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 i in revs:
286 286 ctx = web.repo[i]
287 287 n = ctx.node()
288 288 showtags = webutil.showtag(web.repo, tmpl, 'changelogtag', n)
289 289 files = webutil.listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
290 290
291 291 curcount += 1
292 292 if curcount > revcount + 1:
293 293 break
294 294 yield {"parity": parity.next(),
295 295 "author": ctx.user(),
296 296 "parent": webutil.parents(ctx, i - 1),
297 297 "child": webutil.children(ctx, i + 1),
298 298 "changelogtag": showtags,
299 299 "desc": ctx.description(),
300 300 "extra": ctx.extra(),
301 301 "date": ctx.date(),
302 302 "files": files,
303 303 "rev": i,
304 304 "node": hex(n),
305 305 "tags": webutil.nodetagsdict(web.repo, n),
306 306 "bookmarks": webutil.nodebookmarksdict(web.repo, n),
307 307 "inbranch": webutil.nodeinbranch(web.repo, ctx),
308 308 "branches": webutil.nodebranchdict(web.repo, ctx)
309 309 }
310 310
311 311 revcount = shortlog and web.maxshortchanges or web.maxchanges
312 312 if 'revcount' in req.form:
313 313 try:
314 314 revcount = int(req.form.get('revcount', [revcount])[0])
315 315 revcount = max(revcount, 1)
316 316 tmpl.defaults['sessionvars']['revcount'] = revcount
317 317 except ValueError:
318 318 pass
319 319
320 320 lessvars = copy.copy(tmpl.defaults['sessionvars'])
321 321 lessvars['revcount'] = max(revcount / 2, 1)
322 322 morevars = copy.copy(tmpl.defaults['sessionvars'])
323 323 morevars['revcount'] = revcount * 2
324 324
325 325 count = len(web.repo)
326 326 pos = ctx.rev()
327 327 parity = paritygen(web.stripecount)
328 328
329 329 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
330 330
331 331 entries = list(changelist())
332 332 latestentry = entries[:1]
333 333 if len(entries) > revcount:
334 334 nextentry = entries[-1:]
335 335 entries = entries[:-1]
336 336 else:
337 337 nextentry = []
338 338
339 339 return tmpl(shortlog and 'shortlog' or 'changelog', changenav=changenav,
340 340 node=ctx.hex(), rev=pos, changesets=count,
341 341 entries=entries,
342 342 latestentry=latestentry, nextentry=nextentry,
343 343 archives=web.archivelist("tip"), revcount=revcount,
344 344 morevars=morevars, lessvars=lessvars, query=query)
345 345
346 346 def shortlog(web, req, tmpl):
347 347 return changelog(web, req, tmpl, shortlog=True)
348 348
349 349 def changeset(web, req, tmpl):
350 350 ctx = webutil.changectx(web.repo, req)
351 351 basectx = webutil.basechangectx(web.repo, req)
352 352 if basectx is None:
353 353 basectx = ctx.p1()
354 354 showtags = webutil.showtag(web.repo, tmpl, 'changesettag', ctx.node())
355 355 showbookmarks = webutil.showbookmark(web.repo, tmpl, 'changesetbookmark',
356 356 ctx.node())
357 357 showbranch = webutil.nodebranchnodefault(ctx)
358 358
359 359 files = []
360 360 parity = paritygen(web.stripecount)
361 361 for blockno, f in enumerate(ctx.files()):
362 362 template = f in ctx and 'filenodelink' or 'filenolink'
363 363 files.append(tmpl(template,
364 364 node=ctx.hex(), file=f, blockno=blockno + 1,
365 365 parity=parity.next()))
366 366
367 367 style = web.config('web', 'style', 'paper')
368 368 if 'style' in req.form:
369 369 style = req.form['style'][0]
370 370
371 371 parity = paritygen(web.stripecount)
372 372 diffs = webutil.diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
373 373
374 374 parity = paritygen(web.stripecount)
375 375 diffstatgen = webutil.diffstatgen(ctx, basectx)
376 376 diffstat = webutil.diffstat(tmpl, ctx, diffstatgen, parity)
377 377
378 378 return tmpl('changeset',
379 379 diff=diffs,
380 380 rev=ctx.rev(),
381 381 node=ctx.hex(),
382 382 parent=webutil.parents(ctx),
383 383 child=webutil.children(ctx),
384 384 basenode=basectx.hex(),
385 385 changesettag=showtags,
386 386 changesetbookmark=showbookmarks,
387 387 changesetbranch=showbranch,
388 388 author=ctx.user(),
389 389 desc=ctx.description(),
390 390 extra=ctx.extra(),
391 391 date=ctx.date(),
392 392 files=files,
393 393 diffsummary=lambda **x: webutil.diffsummary(diffstatgen),
394 394 diffstat=diffstat,
395 395 archives=web.archivelist(ctx.hex()),
396 396 tags=webutil.nodetagsdict(web.repo, ctx.node()),
397 397 bookmarks=webutil.nodebookmarksdict(web.repo, ctx.node()),
398 398 branch=webutil.nodebranchnodefault(ctx),
399 399 inbranch=webutil.nodeinbranch(web.repo, ctx),
400 400 branches=webutil.nodebranchdict(web.repo, ctx))
401 401
402 402 rev = changeset
403 403
404 404 def decodepath(path):
405 405 """Hook for mapping a path in the repository to a path in the
406 406 working copy.
407 407
408 408 Extensions (e.g., largefiles) can override this to remap files in
409 409 the virtual file system presented by the manifest command below."""
410 410 return path
411 411
412 412 def manifest(web, req, tmpl):
413 413 ctx = webutil.changectx(web.repo, req)
414 414 path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
415 415 mf = ctx.manifest()
416 416 node = ctx.node()
417 417
418 418 files = {}
419 419 dirs = {}
420 420 parity = paritygen(web.stripecount)
421 421
422 422 if path and path[-1] != "/":
423 423 path += "/"
424 424 l = len(path)
425 425 abspath = "/" + path
426 426
427 427 for full, n in mf.iteritems():
428 428 # the virtual path (working copy path) used for the full
429 429 # (repository) path
430 430 f = decodepath(full)
431 431
432 432 if f[:l] != path:
433 433 continue
434 434 remain = f[l:]
435 435 elements = remain.split('/')
436 436 if len(elements) == 1:
437 437 files[remain] = full
438 438 else:
439 439 h = dirs # need to retain ref to dirs (root)
440 440 for elem in elements[0:-1]:
441 441 if elem not in h:
442 442 h[elem] = {}
443 443 h = h[elem]
444 444 if len(h) > 1:
445 445 break
446 446 h[None] = None # denotes files present
447 447
448 448 if mf and not files and not dirs:
449 449 raise ErrorResponse(HTTP_NOT_FOUND, 'path not found: ' + path)
450 450
451 451 def filelist(**map):
452 452 for f in sorted(files):
453 453 full = files[f]
454 454
455 455 fctx = ctx.filectx(full)
456 456 yield {"file": full,
457 457 "parity": parity.next(),
458 458 "basename": f,
459 459 "date": fctx.date(),
460 460 "size": fctx.size(),
461 461 "permissions": mf.flags(full)}
462 462
463 463 def dirlist(**map):
464 464 for d in sorted(dirs):
465 465
466 466 emptydirs = []
467 467 h = dirs[d]
468 468 while isinstance(h, dict) and len(h) == 1:
469 469 k, v = h.items()[0]
470 470 if v:
471 471 emptydirs.append(k)
472 472 h = v
473 473
474 474 path = "%s%s" % (abspath, d)
475 475 yield {"parity": parity.next(),
476 476 "path": path,
477 477 "emptydirs": "/".join(emptydirs),
478 478 "basename": d}
479 479
480 480 return tmpl("manifest",
481 481 rev=ctx.rev(),
482 482 node=hex(node),
483 483 path=abspath,
484 484 up=webutil.up(abspath),
485 485 upparity=parity.next(),
486 486 fentries=filelist,
487 487 dentries=dirlist,
488 488 archives=web.archivelist(hex(node)),
489 489 tags=webutil.nodetagsdict(web.repo, node),
490 490 bookmarks=webutil.nodebookmarksdict(web.repo, node),
491 491 inbranch=webutil.nodeinbranch(web.repo, ctx),
492 492 branches=webutil.nodebranchdict(web.repo, ctx))
493 493
494 494 def tags(web, req, tmpl):
495 495 i = list(reversed(web.repo.tagslist()))
496 496 parity = paritygen(web.stripecount)
497 497
498 498 def entries(notip, latestonly, **map):
499 499 t = i
500 500 if notip:
501 501 t = [(k, n) for k, n in i if k != "tip"]
502 502 if latestonly:
503 503 t = t[:1]
504 504 for k, n in t:
505 505 yield {"parity": parity.next(),
506 506 "tag": k,
507 507 "date": web.repo[n].date(),
508 508 "node": hex(n)}
509 509
510 510 return tmpl("tags",
511 511 node=hex(web.repo.changelog.tip()),
512 512 entries=lambda **x: entries(False, False, **x),
513 513 entriesnotip=lambda **x: entries(True, False, **x),
514 514 latestentry=lambda **x: entries(True, True, **x))
515 515
516 516 def bookmarks(web, req, tmpl):
517 517 i = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
518 518 parity = paritygen(web.stripecount)
519 519
520 520 def entries(latestonly, **map):
521 521 if latestonly:
522 522 t = [min(i)]
523 523 else:
524 524 t = sorted(i)
525 525 for k, n in t:
526 526 yield {"parity": parity.next(),
527 527 "bookmark": k,
528 528 "date": web.repo[n].date(),
529 529 "node": hex(n)}
530 530
531 531 return tmpl("bookmarks",
532 532 node=hex(web.repo.changelog.tip()),
533 533 entries=lambda **x: entries(latestonly=False, **x),
534 534 latestentry=lambda **x: entries(latestonly=True, **x))
535 535
536 536 def branches(web, req, tmpl):
537 537 tips = []
538 538 heads = web.repo.heads()
539 539 parity = paritygen(web.stripecount)
540 sortkey = lambda ctx: (not ctx.closesbranch(), ctx.rev())
540 sortkey = lambda item: (not item[1], item[0].rev())
541 541
542 542 def entries(limit, **map):
543 543 count = 0
544 544 if not tips:
545 for t, n in web.repo.branchtags().iteritems():
546 tips.append(web.repo[n])
547 for ctx in sorted(tips, key=sortkey, reverse=True):
545 for tag, hs, tip, closed in web.repo.branchmap().iterbranches():
546 tips.append((web.repo[tip], closed))
547 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
548 548 if limit > 0 and count >= limit:
549 549 return
550 550 count += 1
551 if not web.repo.branchheads(ctx.branch()):
551 if closed:
552 552 status = 'closed'
553 553 elif ctx.node() not in heads:
554 554 status = 'inactive'
555 555 else:
556 556 status = 'open'
557 557 yield {'parity': parity.next(),
558 558 'branch': ctx.branch(),
559 559 'status': status,
560 560 'node': ctx.hex(),
561 561 'date': ctx.date()}
562 562
563 563 return tmpl('branches', node=hex(web.repo.changelog.tip()),
564 564 entries=lambda **x: entries(0, **x),
565 565 latestentry=lambda **x: entries(1, **x))
566 566
567 567 def summary(web, req, tmpl):
568 568 i = reversed(web.repo.tagslist())
569 569
570 570 def tagentries(**map):
571 571 parity = paritygen(web.stripecount)
572 572 count = 0
573 573 for k, n in i:
574 574 if k == "tip": # skip tip
575 575 continue
576 576
577 577 count += 1
578 578 if count > 10: # limit to 10 tags
579 579 break
580 580
581 581 yield tmpl("tagentry",
582 582 parity=parity.next(),
583 583 tag=k,
584 584 node=hex(n),
585 585 date=web.repo[n].date())
586 586
587 587 def bookmarks(**map):
588 588 parity = paritygen(web.stripecount)
589 589 marks = [b for b in web.repo._bookmarks.items() if b[1] in web.repo]
590 590 for k, n in sorted(marks)[:10]: # limit to 10 bookmarks
591 591 yield {'parity': parity.next(),
592 592 'bookmark': k,
593 593 'date': web.repo[n].date(),
594 594 'node': hex(n)}
595 595
596 596 def branches(**map):
597 597 parity = paritygen(web.stripecount)
598 598
599 599 b = web.repo.branchmap()
600 600 l = [(-web.repo.changelog.rev(tip), tip, tag)
601 601 for tag, heads, tip, closed in b.iterbranches()]
602 602 for r, n, t in sorted(l):
603 603 yield {'parity': parity.next(),
604 604 'branch': t,
605 605 'node': hex(n),
606 606 'date': web.repo[n].date()}
607 607
608 608 def changelist(**map):
609 609 parity = paritygen(web.stripecount, offset=start - end)
610 610 l = [] # build a list in forward order for efficiency
611 611 revs = []
612 612 if start < end:
613 613 revs = web.repo.changelog.revs(start, end - 1)
614 614 for i in revs:
615 615 ctx = web.repo[i]
616 616 n = ctx.node()
617 617 hn = hex(n)
618 618
619 619 l.append(tmpl(
620 620 'shortlogentry',
621 621 parity=parity.next(),
622 622 author=ctx.user(),
623 623 desc=ctx.description(),
624 624 extra=ctx.extra(),
625 625 date=ctx.date(),
626 626 rev=i,
627 627 node=hn,
628 628 tags=webutil.nodetagsdict(web.repo, n),
629 629 bookmarks=webutil.nodebookmarksdict(web.repo, n),
630 630 inbranch=webutil.nodeinbranch(web.repo, ctx),
631 631 branches=webutil.nodebranchdict(web.repo, ctx)))
632 632
633 633 l.reverse()
634 634 yield l
635 635
636 636 tip = web.repo['tip']
637 637 count = len(web.repo)
638 638 start = max(0, count - web.maxchanges)
639 639 end = min(count, start + web.maxchanges)
640 640
641 641 return tmpl("summary",
642 642 desc=web.config("web", "description", "unknown"),
643 643 owner=get_contact(web.config) or "unknown",
644 644 lastchange=tip.date(),
645 645 tags=tagentries,
646 646 bookmarks=bookmarks,
647 647 branches=branches,
648 648 shortlog=changelist,
649 649 node=tip.hex(),
650 650 archives=web.archivelist("tip"))
651 651
652 652 def filediff(web, req, tmpl):
653 653 fctx, ctx = None, None
654 654 try:
655 655 fctx = webutil.filectx(web.repo, req)
656 656 except LookupError:
657 657 ctx = webutil.changectx(web.repo, req)
658 658 path = webutil.cleanpath(web.repo, req.form['file'][0])
659 659 if path not in ctx.files():
660 660 raise
661 661
662 662 if fctx is not None:
663 663 n = fctx.node()
664 664 path = fctx.path()
665 665 ctx = fctx.changectx()
666 666 else:
667 667 n = ctx.node()
668 668 # path already defined in except clause
669 669
670 670 parity = paritygen(web.stripecount)
671 671 style = web.config('web', 'style', 'paper')
672 672 if 'style' in req.form:
673 673 style = req.form['style'][0]
674 674
675 675 diffs = webutil.diffs(web.repo, tmpl, ctx, None, [path], parity, style)
676 676 rename = fctx and webutil.renamelink(fctx) or []
677 677 ctx = fctx and fctx or ctx
678 678 return tmpl("filediff",
679 679 file=path,
680 680 node=hex(n),
681 681 rev=ctx.rev(),
682 682 date=ctx.date(),
683 683 desc=ctx.description(),
684 684 extra=ctx.extra(),
685 685 author=ctx.user(),
686 686 rename=rename,
687 687 branch=webutil.nodebranchnodefault(ctx),
688 688 parent=webutil.parents(ctx),
689 689 child=webutil.children(ctx),
690 690 diff=diffs)
691 691
692 692 diff = filediff
693 693
694 694 def comparison(web, req, tmpl):
695 695 ctx = webutil.changectx(web.repo, req)
696 696 if 'file' not in req.form:
697 697 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
698 698 path = webutil.cleanpath(web.repo, req.form['file'][0])
699 699 rename = path in ctx and webutil.renamelink(ctx[path]) or []
700 700
701 701 parsecontext = lambda v: v == 'full' and -1 or int(v)
702 702 if 'context' in req.form:
703 703 context = parsecontext(req.form['context'][0])
704 704 else:
705 705 context = parsecontext(web.config('web', 'comparisoncontext', '5'))
706 706
707 707 def filelines(f):
708 708 if util.binary(f.data()):
709 709 mt = mimetypes.guess_type(f.path())[0]
710 710 if not mt:
711 711 mt = 'application/octet-stream'
712 712 return [_('(binary file %s, hash: %s)') % (mt, hex(f.filenode()))]
713 713 return f.data().splitlines()
714 714
715 715 if path in ctx:
716 716 fctx = ctx[path]
717 717 rightrev = fctx.filerev()
718 718 rightnode = fctx.filenode()
719 719 rightlines = filelines(fctx)
720 720 parents = fctx.parents()
721 721 if not parents:
722 722 leftrev = -1
723 723 leftnode = nullid
724 724 leftlines = ()
725 725 else:
726 726 pfctx = parents[0]
727 727 leftrev = pfctx.filerev()
728 728 leftnode = pfctx.filenode()
729 729 leftlines = filelines(pfctx)
730 730 else:
731 731 rightrev = -1
732 732 rightnode = nullid
733 733 rightlines = ()
734 734 fctx = ctx.parents()[0][path]
735 735 leftrev = fctx.filerev()
736 736 leftnode = fctx.filenode()
737 737 leftlines = filelines(fctx)
738 738
739 739 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
740 740 return tmpl('filecomparison',
741 741 file=path,
742 742 node=hex(ctx.node()),
743 743 rev=ctx.rev(),
744 744 date=ctx.date(),
745 745 desc=ctx.description(),
746 746 extra=ctx.extra(),
747 747 author=ctx.user(),
748 748 rename=rename,
749 749 branch=webutil.nodebranchnodefault(ctx),
750 750 parent=webutil.parents(fctx),
751 751 child=webutil.children(fctx),
752 752 leftrev=leftrev,
753 753 leftnode=hex(leftnode),
754 754 rightrev=rightrev,
755 755 rightnode=hex(rightnode),
756 756 comparison=comparison)
757 757
758 758 def annotate(web, req, tmpl):
759 759 fctx = webutil.filectx(web.repo, req)
760 760 f = fctx.path()
761 761 parity = paritygen(web.stripecount)
762 762 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
763 763
764 764 def annotate(**map):
765 765 last = None
766 766 if util.binary(fctx.data()):
767 767 mt = (mimetypes.guess_type(fctx.path())[0]
768 768 or 'application/octet-stream')
769 769 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
770 770 '(binary:%s)' % mt)])
771 771 else:
772 772 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
773 773 diffopts=diffopts))
774 774 for lineno, ((f, targetline), l) in lines:
775 775 fnode = f.filenode()
776 776
777 777 if last != fnode:
778 778 last = fnode
779 779
780 780 yield {"parity": parity.next(),
781 781 "node": f.hex(),
782 782 "rev": f.rev(),
783 783 "author": f.user(),
784 784 "desc": f.description(),
785 785 "extra": f.extra(),
786 786 "file": f.path(),
787 787 "targetline": targetline,
788 788 "line": l,
789 789 "lineid": "l%d" % (lineno + 1),
790 790 "linenumber": "% 6d" % (lineno + 1),
791 791 "revdate": f.date()}
792 792
793 793 return tmpl("fileannotate",
794 794 file=f,
795 795 annotate=annotate,
796 796 path=webutil.up(f),
797 797 rev=fctx.rev(),
798 798 node=fctx.hex(),
799 799 author=fctx.user(),
800 800 date=fctx.date(),
801 801 desc=fctx.description(),
802 802 extra=fctx.extra(),
803 803 rename=webutil.renamelink(fctx),
804 804 branch=webutil.nodebranchnodefault(fctx),
805 805 parent=webutil.parents(fctx),
806 806 child=webutil.children(fctx),
807 807 permissions=fctx.manifest().flags(f))
808 808
809 809 def filelog(web, req, tmpl):
810 810
811 811 try:
812 812 fctx = webutil.filectx(web.repo, req)
813 813 f = fctx.path()
814 814 fl = fctx.filelog()
815 815 except error.LookupError:
816 816 f = webutil.cleanpath(web.repo, req.form['file'][0])
817 817 fl = web.repo.file(f)
818 818 numrevs = len(fl)
819 819 if not numrevs: # file doesn't exist at all
820 820 raise
821 821 rev = webutil.changectx(web.repo, req).rev()
822 822 first = fl.linkrev(0)
823 823 if rev < first: # current rev is from before file existed
824 824 raise
825 825 frev = numrevs - 1
826 826 while fl.linkrev(frev) > rev:
827 827 frev -= 1
828 828 fctx = web.repo.filectx(f, fl.linkrev(frev))
829 829
830 830 revcount = web.maxshortchanges
831 831 if 'revcount' in req.form:
832 832 try:
833 833 revcount = int(req.form.get('revcount', [revcount])[0])
834 834 revcount = max(revcount, 1)
835 835 tmpl.defaults['sessionvars']['revcount'] = revcount
836 836 except ValueError:
837 837 pass
838 838
839 839 lessvars = copy.copy(tmpl.defaults['sessionvars'])
840 840 lessvars['revcount'] = max(revcount / 2, 1)
841 841 morevars = copy.copy(tmpl.defaults['sessionvars'])
842 842 morevars['revcount'] = revcount * 2
843 843
844 844 count = fctx.filerev() + 1
845 845 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
846 846 end = min(count, start + revcount) # last rev on this page
847 847 parity = paritygen(web.stripecount, offset=start - end)
848 848
849 849 def entries():
850 850 l = []
851 851
852 852 repo = web.repo
853 853 revs = fctx.filelog().revs(start, end - 1)
854 854 for i in revs:
855 855 iterfctx = fctx.filectx(i)
856 856
857 857 l.append({"parity": parity.next(),
858 858 "filerev": i,
859 859 "file": f,
860 860 "node": iterfctx.hex(),
861 861 "author": iterfctx.user(),
862 862 "date": iterfctx.date(),
863 863 "rename": webutil.renamelink(iterfctx),
864 864 "parent": webutil.parents(iterfctx),
865 865 "child": webutil.children(iterfctx),
866 866 "desc": iterfctx.description(),
867 867 "extra": iterfctx.extra(),
868 868 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
869 869 "bookmarks": webutil.nodebookmarksdict(
870 870 repo, iterfctx.node()),
871 871 "branch": webutil.nodebranchnodefault(iterfctx),
872 872 "inbranch": webutil.nodeinbranch(repo, iterfctx),
873 873 "branches": webutil.nodebranchdict(repo, iterfctx)})
874 874 for e in reversed(l):
875 875 yield e
876 876
877 877 entries = list(entries())
878 878 latestentry = entries[:1]
879 879
880 880 revnav = webutil.filerevnav(web.repo, fctx.path())
881 881 nav = revnav.gen(end - 1, revcount, count)
882 882 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
883 883 entries=entries,
884 884 latestentry=latestentry,
885 885 revcount=revcount, morevars=morevars, lessvars=lessvars)
886 886
887 887 def archive(web, req, tmpl):
888 888 type_ = req.form.get('type', [None])[0]
889 889 allowed = web.configlist("web", "allow_archive")
890 890 key = req.form['node'][0]
891 891
892 892 if type_ not in web.archives:
893 893 msg = 'Unsupported archive type: %s' % type_
894 894 raise ErrorResponse(HTTP_NOT_FOUND, msg)
895 895
896 896 if not ((type_ in allowed or
897 897 web.configbool("web", "allow" + type_, False))):
898 898 msg = 'Archive type not allowed: %s' % type_
899 899 raise ErrorResponse(HTTP_FORBIDDEN, msg)
900 900
901 901 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
902 902 cnode = web.repo.lookup(key)
903 903 arch_version = key
904 904 if cnode == key or key == 'tip':
905 905 arch_version = short(cnode)
906 906 name = "%s-%s" % (reponame, arch_version)
907 907
908 908 ctx = webutil.changectx(web.repo, req)
909 909 pats = []
910 910 matchfn = None
911 911 file = req.form.get('file', None)
912 912 if file:
913 913 pats = ['path:' + file[0]]
914 914 matchfn = scmutil.match(ctx, pats, default='path')
915 915 if pats:
916 916 files = [f for f in ctx.manifest().keys() if matchfn(f)]
917 917 if not files:
918 918 raise ErrorResponse(HTTP_NOT_FOUND,
919 919 'file(s) not found: %s' % file[0])
920 920
921 921 mimetype, artype, extension, encoding = web.archive_specs[type_]
922 922 headers = [
923 923 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
924 924 ]
925 925 if encoding:
926 926 headers.append(('Content-Encoding', encoding))
927 927 req.headers.extend(headers)
928 928 req.respond(HTTP_OK, mimetype)
929 929
930 930 archival.archive(web.repo, req, cnode, artype, prefix=name,
931 931 matchfn=matchfn,
932 932 subrepos=web.configbool("web", "archivesubrepos"))
933 933 return []
934 934
935 935
936 936 def static(web, req, tmpl):
937 937 fname = req.form['file'][0]
938 938 # a repo owner may set web.static in .hg/hgrc to get any file
939 939 # readable by the user running the CGI script
940 940 static = web.config("web", "static", None, untrusted=False)
941 941 if not static:
942 942 tp = web.templatepath or templater.templatepath()
943 943 if isinstance(tp, str):
944 944 tp = [tp]
945 945 static = [os.path.join(p, 'static') for p in tp]
946 946 staticfile(static, fname, req)
947 947 return []
948 948
949 949 def graph(web, req, tmpl):
950 950
951 951 ctx = webutil.changectx(web.repo, req)
952 952 rev = ctx.rev()
953 953
954 954 bg_height = 39
955 955 revcount = web.maxshortchanges
956 956 if 'revcount' in req.form:
957 957 try:
958 958 revcount = int(req.form.get('revcount', [revcount])[0])
959 959 revcount = max(revcount, 1)
960 960 tmpl.defaults['sessionvars']['revcount'] = revcount
961 961 except ValueError:
962 962 pass
963 963
964 964 lessvars = copy.copy(tmpl.defaults['sessionvars'])
965 965 lessvars['revcount'] = max(revcount / 2, 1)
966 966 morevars = copy.copy(tmpl.defaults['sessionvars'])
967 967 morevars['revcount'] = revcount * 2
968 968
969 969 count = len(web.repo)
970 970 pos = rev
971 971
972 972 uprev = min(max(0, count - 1), rev + revcount)
973 973 downrev = max(0, rev - revcount)
974 974 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
975 975
976 976 tree = []
977 977 if pos != -1:
978 978 allrevs = web.repo.changelog.revs(pos, 0)
979 979 revs = []
980 980 for i in allrevs:
981 981 revs.append(i)
982 982 if len(revs) >= revcount:
983 983 break
984 984
985 985 dag = graphmod.dagwalker(web.repo, revs)
986 986 tree = list(graphmod.colored(dag, web.repo))
987 987
988 988 def getcolumns(tree):
989 989 cols = 0
990 990 for (id, type, ctx, vtx, edges) in tree:
991 991 if type != graphmod.CHANGESET:
992 992 continue
993 993 cols = max(cols, max([edge[0] for edge in edges] or [0]),
994 994 max([edge[1] for edge in edges] or [0]))
995 995 return cols
996 996
997 997 def graphdata(usetuples, **map):
998 998 data = []
999 999
1000 1000 row = 0
1001 1001 for (id, type, ctx, vtx, edges) in tree:
1002 1002 if type != graphmod.CHANGESET:
1003 1003 continue
1004 1004 node = str(ctx)
1005 1005 age = templatefilters.age(ctx.date())
1006 1006 desc = templatefilters.firstline(ctx.description())
1007 1007 desc = cgi.escape(templatefilters.nonempty(desc))
1008 1008 user = cgi.escape(templatefilters.person(ctx.user()))
1009 1009 branch = cgi.escape(ctx.branch())
1010 1010 try:
1011 1011 branchnode = web.repo.branchtip(branch)
1012 1012 except error.RepoLookupError:
1013 1013 branchnode = None
1014 1014 branch = branch, branchnode == ctx.node()
1015 1015
1016 1016 if usetuples:
1017 1017 data.append((node, vtx, edges, desc, user, age, branch,
1018 1018 [cgi.escape(x) for x in ctx.tags()],
1019 1019 [cgi.escape(x) for x in ctx.bookmarks()]))
1020 1020 else:
1021 1021 edgedata = [dict(col=edge[0], nextcol=edge[1],
1022 1022 color=(edge[2] - 1) % 6 + 1,
1023 1023 width=edge[3], bcolor=edge[4])
1024 1024 for edge in edges]
1025 1025
1026 1026 data.append(
1027 1027 dict(node=node,
1028 1028 col=vtx[0],
1029 1029 color=(vtx[1] - 1) % 6 + 1,
1030 1030 edges=edgedata,
1031 1031 row=row,
1032 1032 nextrow=row + 1,
1033 1033 desc=desc,
1034 1034 user=user,
1035 1035 age=age,
1036 1036 bookmarks=webutil.nodebookmarksdict(
1037 1037 web.repo, ctx.node()),
1038 1038 branches=webutil.nodebranchdict(web.repo, ctx),
1039 1039 inbranch=webutil.nodeinbranch(web.repo, ctx),
1040 1040 tags=webutil.nodetagsdict(web.repo, ctx.node())))
1041 1041
1042 1042 row += 1
1043 1043
1044 1044 return data
1045 1045
1046 1046 cols = getcolumns(tree)
1047 1047 rows = len(tree)
1048 1048 canvasheight = (rows + 1) * bg_height - 27
1049 1049
1050 1050 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
1051 1051 lessvars=lessvars, morevars=morevars, downrev=downrev,
1052 1052 cols=cols, rows=rows,
1053 1053 canvaswidth=(cols + 1) * bg_height,
1054 1054 truecanvasheight=rows * bg_height,
1055 1055 canvasheight=canvasheight, bg_height=bg_height,
1056 1056 jsdata=lambda **x: graphdata(True, **x),
1057 1057 nodes=lambda **x: graphdata(False, **x),
1058 1058 node=ctx.hex(), changenav=changenav)
1059 1059
1060 1060 def _getdoc(e):
1061 1061 doc = e[0].__doc__
1062 1062 if doc:
1063 1063 doc = _(doc).split('\n')[0]
1064 1064 else:
1065 1065 doc = _('(no help text available)')
1066 1066 return doc
1067 1067
1068 1068 def help(web, req, tmpl):
1069 1069 from mercurial import commands # avoid cycle
1070 1070
1071 1071 topicname = req.form.get('node', [None])[0]
1072 1072 if not topicname:
1073 1073 def topics(**map):
1074 1074 for entries, summary, _ in helpmod.helptable:
1075 1075 yield {'topic': entries[0], 'summary': summary}
1076 1076
1077 1077 early, other = [], []
1078 1078 primary = lambda s: s.split('|')[0]
1079 1079 for c, e in commands.table.iteritems():
1080 1080 doc = _getdoc(e)
1081 1081 if 'DEPRECATED' in doc or c.startswith('debug'):
1082 1082 continue
1083 1083 cmd = primary(c)
1084 1084 if cmd.startswith('^'):
1085 1085 early.append((cmd[1:], doc))
1086 1086 else:
1087 1087 other.append((cmd, doc))
1088 1088
1089 1089 early.sort()
1090 1090 other.sort()
1091 1091
1092 1092 def earlycommands(**map):
1093 1093 for c, doc in early:
1094 1094 yield {'topic': c, 'summary': doc}
1095 1095
1096 1096 def othercommands(**map):
1097 1097 for c, doc in other:
1098 1098 yield {'topic': c, 'summary': doc}
1099 1099
1100 1100 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1101 1101 othercommands=othercommands, title='Index')
1102 1102
1103 1103 u = webutil.wsgiui()
1104 1104 u.verbose = True
1105 1105 try:
1106 1106 doc = helpmod.help_(u, topicname)
1107 1107 except error.UnknownCommand:
1108 1108 raise ErrorResponse(HTTP_NOT_FOUND)
1109 1109 return tmpl('help', topic=topicname, doc=doc)
General Comments 0
You need to be logged in to leave comments. Login now