##// END OF EJS Templates
webcommands.annotate: explicitly only honor whitespace diffopts...
Siddharth Agarwal -
r23689:4fedf2a9 default
parent child Browse files
Show More
@@ -1,1107 +1,1108 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 190 revs = mfunc(web.repo, revset.baseset(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 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 545 for tag, hs, tip, closed in web.repo.branchmap().iterbranches():
546 546 tips.append((web.repo[tip], closed))
547 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 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 parent = ctx.p1()
716 716 leftrev = parent.rev()
717 717 leftnode = parent.node()
718 718 rightrev = ctx.rev()
719 719 rightnode = ctx.node()
720 720 if path in ctx:
721 721 fctx = ctx[path]
722 722 rightlines = filelines(fctx)
723 723 if path not in parent:
724 724 leftlines = ()
725 725 else:
726 726 pfctx = parent[path]
727 727 leftlines = filelines(pfctx)
728 728 else:
729 729 rightlines = ()
730 730 fctx = ctx.parents()[0][path]
731 731 leftlines = filelines(fctx)
732 732
733 733 comparison = webutil.compare(tmpl, context, leftlines, rightlines)
734 734 return tmpl('filecomparison',
735 735 file=path,
736 736 node=hex(ctx.node()),
737 737 rev=ctx.rev(),
738 738 date=ctx.date(),
739 739 desc=ctx.description(),
740 740 extra=ctx.extra(),
741 741 author=ctx.user(),
742 742 rename=rename,
743 743 branch=webutil.nodebranchnodefault(ctx),
744 744 parent=webutil.parents(fctx),
745 745 child=webutil.children(fctx),
746 746 leftrev=leftrev,
747 747 leftnode=hex(leftnode),
748 748 rightrev=rightrev,
749 749 rightnode=hex(rightnode),
750 750 comparison=comparison)
751 751
752 752 def annotate(web, req, tmpl):
753 753 fctx = webutil.filectx(web.repo, req)
754 754 f = fctx.path()
755 755 parity = paritygen(web.stripecount)
756 diffopts = patch.diffopts(web.repo.ui, untrusted=True, section='annotate')
756 diffopts = patch.difffeatureopts(web.repo.ui, untrusted=True,
757 section='annotate', whitespace=True)
757 758
758 759 def annotate(**map):
759 760 last = None
760 761 if util.binary(fctx.data()):
761 762 mt = (mimetypes.guess_type(fctx.path())[0]
762 763 or 'application/octet-stream')
763 764 lines = enumerate([((fctx.filectx(fctx.filerev()), 1),
764 765 '(binary:%s)' % mt)])
765 766 else:
766 767 lines = enumerate(fctx.annotate(follow=True, linenumber=True,
767 768 diffopts=diffopts))
768 769 for lineno, ((f, targetline), l) in lines:
769 770 fnode = f.filenode()
770 771
771 772 if last != fnode:
772 773 last = fnode
773 774
774 775 yield {"parity": parity.next(),
775 776 "node": f.hex(),
776 777 "rev": f.rev(),
777 778 "author": f.user(),
778 779 "desc": f.description(),
779 780 "extra": f.extra(),
780 781 "file": f.path(),
781 782 "targetline": targetline,
782 783 "line": l,
783 784 "lineid": "l%d" % (lineno + 1),
784 785 "linenumber": "% 6d" % (lineno + 1),
785 786 "revdate": f.date()}
786 787
787 788 return tmpl("fileannotate",
788 789 file=f,
789 790 annotate=annotate,
790 791 path=webutil.up(f),
791 792 rev=fctx.rev(),
792 793 node=fctx.hex(),
793 794 author=fctx.user(),
794 795 date=fctx.date(),
795 796 desc=fctx.description(),
796 797 extra=fctx.extra(),
797 798 rename=webutil.renamelink(fctx),
798 799 branch=webutil.nodebranchnodefault(fctx),
799 800 parent=webutil.parents(fctx),
800 801 child=webutil.children(fctx),
801 802 permissions=fctx.manifest().flags(f))
802 803
803 804 def filelog(web, req, tmpl):
804 805
805 806 try:
806 807 fctx = webutil.filectx(web.repo, req)
807 808 f = fctx.path()
808 809 fl = fctx.filelog()
809 810 except error.LookupError:
810 811 f = webutil.cleanpath(web.repo, req.form['file'][0])
811 812 fl = web.repo.file(f)
812 813 numrevs = len(fl)
813 814 if not numrevs: # file doesn't exist at all
814 815 raise
815 816 rev = webutil.changectx(web.repo, req).rev()
816 817 first = fl.linkrev(0)
817 818 if rev < first: # current rev is from before file existed
818 819 raise
819 820 frev = numrevs - 1
820 821 while fl.linkrev(frev) > rev:
821 822 frev -= 1
822 823 fctx = web.repo.filectx(f, fl.linkrev(frev))
823 824
824 825 revcount = web.maxshortchanges
825 826 if 'revcount' in req.form:
826 827 try:
827 828 revcount = int(req.form.get('revcount', [revcount])[0])
828 829 revcount = max(revcount, 1)
829 830 tmpl.defaults['sessionvars']['revcount'] = revcount
830 831 except ValueError:
831 832 pass
832 833
833 834 lessvars = copy.copy(tmpl.defaults['sessionvars'])
834 835 lessvars['revcount'] = max(revcount / 2, 1)
835 836 morevars = copy.copy(tmpl.defaults['sessionvars'])
836 837 morevars['revcount'] = revcount * 2
837 838
838 839 count = fctx.filerev() + 1
839 840 start = max(0, fctx.filerev() - revcount + 1) # first rev on this page
840 841 end = min(count, start + revcount) # last rev on this page
841 842 parity = paritygen(web.stripecount, offset=start - end)
842 843
843 844 def entries():
844 845 l = []
845 846
846 847 repo = web.repo
847 848 revs = fctx.filelog().revs(start, end - 1)
848 849 for i in revs:
849 850 iterfctx = fctx.filectx(i)
850 851
851 852 l.append({"parity": parity.next(),
852 853 "filerev": i,
853 854 "file": f,
854 855 "node": iterfctx.hex(),
855 856 "author": iterfctx.user(),
856 857 "date": iterfctx.date(),
857 858 "rename": webutil.renamelink(iterfctx),
858 859 "parent": webutil.parents(iterfctx),
859 860 "child": webutil.children(iterfctx),
860 861 "desc": iterfctx.description(),
861 862 "extra": iterfctx.extra(),
862 863 "tags": webutil.nodetagsdict(repo, iterfctx.node()),
863 864 "bookmarks": webutil.nodebookmarksdict(
864 865 repo, iterfctx.node()),
865 866 "branch": webutil.nodebranchnodefault(iterfctx),
866 867 "inbranch": webutil.nodeinbranch(repo, iterfctx),
867 868 "branches": webutil.nodebranchdict(repo, iterfctx)})
868 869 for e in reversed(l):
869 870 yield e
870 871
871 872 entries = list(entries())
872 873 latestentry = entries[:1]
873 874
874 875 revnav = webutil.filerevnav(web.repo, fctx.path())
875 876 nav = revnav.gen(end - 1, revcount, count)
876 877 return tmpl("filelog", file=f, node=fctx.hex(), nav=nav,
877 878 entries=entries,
878 879 latestentry=latestentry,
879 880 revcount=revcount, morevars=morevars, lessvars=lessvars)
880 881
881 882 def archive(web, req, tmpl):
882 883 type_ = req.form.get('type', [None])[0]
883 884 allowed = web.configlist("web", "allow_archive")
884 885 key = req.form['node'][0]
885 886
886 887 if type_ not in web.archives:
887 888 msg = 'Unsupported archive type: %s' % type_
888 889 raise ErrorResponse(HTTP_NOT_FOUND, msg)
889 890
890 891 if not ((type_ in allowed or
891 892 web.configbool("web", "allow" + type_, False))):
892 893 msg = 'Archive type not allowed: %s' % type_
893 894 raise ErrorResponse(HTTP_FORBIDDEN, msg)
894 895
895 896 reponame = re.sub(r"\W+", "-", os.path.basename(web.reponame))
896 897 cnode = web.repo.lookup(key)
897 898 arch_version = key
898 899 if cnode == key or key == 'tip':
899 900 arch_version = short(cnode)
900 901 name = "%s-%s" % (reponame, arch_version)
901 902
902 903 ctx = webutil.changectx(web.repo, req)
903 904 pats = []
904 905 matchfn = scmutil.match(ctx, [])
905 906 file = req.form.get('file', None)
906 907 if file:
907 908 pats = ['path:' + file[0]]
908 909 matchfn = scmutil.match(ctx, pats, default='path')
909 910 if pats:
910 911 files = [f for f in ctx.manifest().keys() if matchfn(f)]
911 912 if not files:
912 913 raise ErrorResponse(HTTP_NOT_FOUND,
913 914 'file(s) not found: %s' % file[0])
914 915
915 916 mimetype, artype, extension, encoding = web.archive_specs[type_]
916 917 headers = [
917 918 ('Content-Disposition', 'attachment; filename=%s%s' % (name, extension))
918 919 ]
919 920 if encoding:
920 921 headers.append(('Content-Encoding', encoding))
921 922 req.headers.extend(headers)
922 923 req.respond(HTTP_OK, mimetype)
923 924
924 925 archival.archive(web.repo, req, cnode, artype, prefix=name,
925 926 matchfn=matchfn,
926 927 subrepos=web.configbool("web", "archivesubrepos"))
927 928 return []
928 929
929 930
930 931 def static(web, req, tmpl):
931 932 fname = req.form['file'][0]
932 933 # a repo owner may set web.static in .hg/hgrc to get any file
933 934 # readable by the user running the CGI script
934 935 static = web.config("web", "static", None, untrusted=False)
935 936 if not static:
936 937 tp = web.templatepath or templater.templatepaths()
937 938 if isinstance(tp, str):
938 939 tp = [tp]
939 940 static = [os.path.join(p, 'static') for p in tp]
940 941 staticfile(static, fname, req)
941 942 return []
942 943
943 944 def graph(web, req, tmpl):
944 945
945 946 ctx = webutil.changectx(web.repo, req)
946 947 rev = ctx.rev()
947 948
948 949 bg_height = 39
949 950 revcount = web.maxshortchanges
950 951 if 'revcount' in req.form:
951 952 try:
952 953 revcount = int(req.form.get('revcount', [revcount])[0])
953 954 revcount = max(revcount, 1)
954 955 tmpl.defaults['sessionvars']['revcount'] = revcount
955 956 except ValueError:
956 957 pass
957 958
958 959 lessvars = copy.copy(tmpl.defaults['sessionvars'])
959 960 lessvars['revcount'] = max(revcount / 2, 1)
960 961 morevars = copy.copy(tmpl.defaults['sessionvars'])
961 962 morevars['revcount'] = revcount * 2
962 963
963 964 count = len(web.repo)
964 965 pos = rev
965 966
966 967 uprev = min(max(0, count - 1), rev + revcount)
967 968 downrev = max(0, rev - revcount)
968 969 changenav = webutil.revnav(web.repo).gen(pos, revcount, count)
969 970
970 971 tree = []
971 972 if pos != -1:
972 973 allrevs = web.repo.changelog.revs(pos, 0)
973 974 revs = []
974 975 for i in allrevs:
975 976 revs.append(i)
976 977 if len(revs) >= revcount:
977 978 break
978 979
979 980 # We have to feed a baseset to dagwalker as it is expecting smartset
980 981 # object. This does not have a big impact on hgweb performance itself
981 982 # since hgweb graphing code is not itself lazy yet.
982 983 dag = graphmod.dagwalker(web.repo, revset.baseset(revs))
983 984 # As we said one line above... not lazy.
984 985 tree = list(graphmod.colored(dag, web.repo))
985 986
986 987 def getcolumns(tree):
987 988 cols = 0
988 989 for (id, type, ctx, vtx, edges) in tree:
989 990 if type != graphmod.CHANGESET:
990 991 continue
991 992 cols = max(cols, max([edge[0] for edge in edges] or [0]),
992 993 max([edge[1] for edge in edges] or [0]))
993 994 return cols
994 995
995 996 def graphdata(usetuples, **map):
996 997 data = []
997 998
998 999 row = 0
999 1000 for (id, type, ctx, vtx, edges) in tree:
1000 1001 if type != graphmod.CHANGESET:
1001 1002 continue
1002 1003 node = str(ctx)
1003 1004 age = templatefilters.age(ctx.date())
1004 1005 desc = templatefilters.firstline(ctx.description())
1005 1006 desc = cgi.escape(templatefilters.nonempty(desc))
1006 1007 user = cgi.escape(templatefilters.person(ctx.user()))
1007 1008 branch = cgi.escape(ctx.branch())
1008 1009 try:
1009 1010 branchnode = web.repo.branchtip(branch)
1010 1011 except error.RepoLookupError:
1011 1012 branchnode = None
1012 1013 branch = branch, branchnode == ctx.node()
1013 1014
1014 1015 if usetuples:
1015 1016 data.append((node, vtx, edges, desc, user, age, branch,
1016 1017 [cgi.escape(x) for x in ctx.tags()],
1017 1018 [cgi.escape(x) for x in ctx.bookmarks()]))
1018 1019 else:
1019 1020 edgedata = [{'col': edge[0], 'nextcol': edge[1],
1020 1021 'color': (edge[2] - 1) % 6 + 1,
1021 1022 'width': edge[3], 'bcolor': edge[4]}
1022 1023 for edge in edges]
1023 1024
1024 1025 data.append(
1025 1026 {'node': node,
1026 1027 'col': vtx[0],
1027 1028 'color': (vtx[1] - 1) % 6 + 1,
1028 1029 'edges': edgedata,
1029 1030 'row': row,
1030 1031 'nextrow': row + 1,
1031 1032 'desc': desc,
1032 1033 'user': user,
1033 1034 'age': age,
1034 1035 'bookmarks': webutil.nodebookmarksdict(
1035 1036 web.repo, ctx.node()),
1036 1037 'branches': webutil.nodebranchdict(web.repo, ctx),
1037 1038 'inbranch': webutil.nodeinbranch(web.repo, ctx),
1038 1039 'tags': webutil.nodetagsdict(web.repo, ctx.node())})
1039 1040
1040 1041 row += 1
1041 1042
1042 1043 return data
1043 1044
1044 1045 cols = getcolumns(tree)
1045 1046 rows = len(tree)
1046 1047 canvasheight = (rows + 1) * bg_height - 27
1047 1048
1048 1049 return tmpl('graph', rev=rev, revcount=revcount, uprev=uprev,
1049 1050 lessvars=lessvars, morevars=morevars, downrev=downrev,
1050 1051 cols=cols, rows=rows,
1051 1052 canvaswidth=(cols + 1) * bg_height,
1052 1053 truecanvasheight=rows * bg_height,
1053 1054 canvasheight=canvasheight, bg_height=bg_height,
1054 1055 jsdata=lambda **x: graphdata(True, **x),
1055 1056 nodes=lambda **x: graphdata(False, **x),
1056 1057 node=ctx.hex(), changenav=changenav)
1057 1058
1058 1059 def _getdoc(e):
1059 1060 doc = e[0].__doc__
1060 1061 if doc:
1061 1062 doc = _(doc).split('\n')[0]
1062 1063 else:
1063 1064 doc = _('(no help text available)')
1064 1065 return doc
1065 1066
1066 1067 def help(web, req, tmpl):
1067 1068 from mercurial import commands # avoid cycle
1068 1069
1069 1070 topicname = req.form.get('node', [None])[0]
1070 1071 if not topicname:
1071 1072 def topics(**map):
1072 1073 for entries, summary, _doc in helpmod.helptable:
1073 1074 yield {'topic': entries[0], 'summary': summary}
1074 1075
1075 1076 early, other = [], []
1076 1077 primary = lambda s: s.split('|')[0]
1077 1078 for c, e in commands.table.iteritems():
1078 1079 doc = _getdoc(e)
1079 1080 if 'DEPRECATED' in doc or c.startswith('debug'):
1080 1081 continue
1081 1082 cmd = primary(c)
1082 1083 if cmd.startswith('^'):
1083 1084 early.append((cmd[1:], doc))
1084 1085 else:
1085 1086 other.append((cmd, doc))
1086 1087
1087 1088 early.sort()
1088 1089 other.sort()
1089 1090
1090 1091 def earlycommands(**map):
1091 1092 for c, doc in early:
1092 1093 yield {'topic': c, 'summary': doc}
1093 1094
1094 1095 def othercommands(**map):
1095 1096 for c, doc in other:
1096 1097 yield {'topic': c, 'summary': doc}
1097 1098
1098 1099 return tmpl('helptopics', topics=topics, earlycommands=earlycommands,
1099 1100 othercommands=othercommands, title='Index')
1100 1101
1101 1102 u = webutil.wsgiui()
1102 1103 u.verbose = True
1103 1104 try:
1104 1105 doc = helpmod.help_(u, topicname)
1105 1106 except error.UnknownCommand:
1106 1107 raise ErrorResponse(HTTP_NOT_FOUND)
1107 1108 return tmpl('help', topic=topicname, doc=doc)
General Comments 0
You need to be logged in to leave comments. Login now