##// END OF EJS Templates
hgweb: ensure _navseq yield strictly increasing numbers...
Pierre-Yves David -
r18392:88a37b19 default
parent child Browse files
Show More
@@ -1,360 +1,360 b''
1 1 # hgweb/webutil.py - utility library for the web interface.
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 import os, copy
10 10 from mercurial import match, patch, scmutil, error, ui, util
11 11 from mercurial.i18n import _
12 12 from mercurial.node import hex, nullid
13 13 from common import ErrorResponse
14 14 from common import HTTP_NOT_FOUND
15 15 import difflib
16 16
17 17 def up(p):
18 18 if p[0] != "/":
19 19 p = "/" + p
20 20 if p[-1] == "/":
21 21 p = p[:-1]
22 22 up = os.path.dirname(p)
23 23 if up == "/":
24 24 return "/"
25 25 return up + "/"
26 26
27 27 def _navseq(step, firststep=None):
28 28 if firststep:
29 29 yield firststep
30 30 if firststep >= 20 and firststep <= 40:
31 yield 50
32 step *= 10
31 firststep = 50
32 yield firststep
33 assert step > 0
34 assert firststep > 0
35 while step <= firststep:
36 step *= 10
33 37 while True:
34 38 yield 1 * step
35 39 yield 3 * step
36 40 step *= 10
37 41
38 42 def revnavgen(pos, pagelen, limit, nodefunc):
39 43 """computes label and revision id for navigation link
40 44
41 45 :pos: is the revision relative to which we generate navigation.
42 46 :pagelen: the size of each navigation page
43 47 :limit: how far shall we link
44 48 :nodefun: factory for a changectx from a revision
45 49
46 50 The return is:
47 51 - a single element tuple
48 52 - containing a dictionary with a `before` and `after` key
49 53 - values are generator functions taking an arbitrary number of kwargs
50 54 - yield items are dictionaries with `label` and `node` keys
51 55 """
52 56
53 57 navbefore = []
54 58 navafter = []
55 59
56 last = 0
57 60 for f in _navseq(1, pagelen):
58 if f < pagelen or f <= last:
59 continue
60 61 if f > limit:
61 62 break
62 last = f
63 63 if pos + f < limit:
64 64 navafter.append(("+%d" % f, hex(nodefunc(pos + f).node())))
65 65 if pos - f >= 0:
66 66 navbefore.insert(0, ("-%d" % f, hex(nodefunc(pos - f).node())))
67 67
68 68 navafter.append(("tip", "tip"))
69 69 try:
70 70 navbefore.insert(0, ("(0)", hex(nodefunc(0).node())))
71 71 except error.RepoError:
72 72 pass
73 73
74 74 data = lambda i: {"label": i[0], "node": i[1]}
75 75 return ({'before': lambda **map: (data(i) for i in navbefore),
76 76 'after': lambda **map: (data(i) for i in navafter)},)
77 77
78 78 def _siblings(siblings=[], hiderev=None):
79 79 siblings = [s for s in siblings if s.node() != nullid]
80 80 if len(siblings) == 1 and siblings[0].rev() == hiderev:
81 81 return
82 82 for s in siblings:
83 83 d = {'node': s.hex(), 'rev': s.rev()}
84 84 d['user'] = s.user()
85 85 d['date'] = s.date()
86 86 d['description'] = s.description()
87 87 d['branch'] = s.branch()
88 88 if util.safehasattr(s, 'path'):
89 89 d['file'] = s.path()
90 90 yield d
91 91
92 92 def parents(ctx, hide=None):
93 93 return _siblings(ctx.parents(), hide)
94 94
95 95 def children(ctx, hide=None):
96 96 return _siblings(ctx.children(), hide)
97 97
98 98 def renamelink(fctx):
99 99 r = fctx.renamed()
100 100 if r:
101 101 return [dict(file=r[0], node=hex(r[1]))]
102 102 return []
103 103
104 104 def nodetagsdict(repo, node):
105 105 return [{"name": i} for i in repo.nodetags(node)]
106 106
107 107 def nodebookmarksdict(repo, node):
108 108 return [{"name": i} for i in repo.nodebookmarks(node)]
109 109
110 110 def nodebranchdict(repo, ctx):
111 111 branches = []
112 112 branch = ctx.branch()
113 113 # If this is an empty repo, ctx.node() == nullid,
114 114 # ctx.branch() == 'default'.
115 115 try:
116 116 branchnode = repo.branchtip(branch)
117 117 except error.RepoLookupError:
118 118 branchnode = None
119 119 if branchnode == ctx.node():
120 120 branches.append({"name": branch})
121 121 return branches
122 122
123 123 def nodeinbranch(repo, ctx):
124 124 branches = []
125 125 branch = ctx.branch()
126 126 try:
127 127 branchnode = repo.branchtip(branch)
128 128 except error.RepoLookupError:
129 129 branchnode = None
130 130 if branch != 'default' and branchnode != ctx.node():
131 131 branches.append({"name": branch})
132 132 return branches
133 133
134 134 def nodebranchnodefault(ctx):
135 135 branches = []
136 136 branch = ctx.branch()
137 137 if branch != 'default':
138 138 branches.append({"name": branch})
139 139 return branches
140 140
141 141 def showtag(repo, tmpl, t1, node=nullid, **args):
142 142 for t in repo.nodetags(node):
143 143 yield tmpl(t1, tag=t, **args)
144 144
145 145 def showbookmark(repo, tmpl, t1, node=nullid, **args):
146 146 for t in repo.nodebookmarks(node):
147 147 yield tmpl(t1, bookmark=t, **args)
148 148
149 149 def cleanpath(repo, path):
150 150 path = path.lstrip('/')
151 151 return scmutil.canonpath(repo.root, '', path)
152 152
153 153 def changeidctx (repo, changeid):
154 154 try:
155 155 ctx = repo[changeid]
156 156 except error.RepoError:
157 157 man = repo.manifest
158 158 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
159 159
160 160 return ctx
161 161
162 162 def changectx (repo, req):
163 163 changeid = "tip"
164 164 if 'node' in req.form:
165 165 changeid = req.form['node'][0]
166 166 ipos=changeid.find(':')
167 167 if ipos != -1:
168 168 changeid = changeid[(ipos + 1):]
169 169 elif 'manifest' in req.form:
170 170 changeid = req.form['manifest'][0]
171 171
172 172 return changeidctx(repo, changeid)
173 173
174 174 def basechangectx(repo, req):
175 175 if 'node' in req.form:
176 176 changeid = req.form['node'][0]
177 177 ipos=changeid.find(':')
178 178 if ipos != -1:
179 179 changeid = changeid[:ipos]
180 180 return changeidctx(repo, changeid)
181 181
182 182 return None
183 183
184 184 def filectx(repo, req):
185 185 if 'file' not in req.form:
186 186 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
187 187 path = cleanpath(repo, req.form['file'][0])
188 188 if 'node' in req.form:
189 189 changeid = req.form['node'][0]
190 190 elif 'filenode' in req.form:
191 191 changeid = req.form['filenode'][0]
192 192 else:
193 193 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
194 194 try:
195 195 fctx = repo[changeid][path]
196 196 except error.RepoError:
197 197 fctx = repo.filectx(path, fileid=changeid)
198 198
199 199 return fctx
200 200
201 201 def listfilediffs(tmpl, files, node, max):
202 202 for f in files[:max]:
203 203 yield tmpl('filedifflink', node=hex(node), file=f)
204 204 if len(files) > max:
205 205 yield tmpl('fileellipses')
206 206
207 207 def diffs(repo, tmpl, ctx, basectx, files, parity, style):
208 208
209 209 def countgen():
210 210 start = 1
211 211 while True:
212 212 yield start
213 213 start += 1
214 214
215 215 blockcount = countgen()
216 216 def prettyprintlines(diff, blockno):
217 217 for lineno, l in enumerate(diff.splitlines(True)):
218 218 lineno = "%d.%d" % (blockno, lineno + 1)
219 219 if l.startswith('+'):
220 220 ltype = "difflineplus"
221 221 elif l.startswith('-'):
222 222 ltype = "difflineminus"
223 223 elif l.startswith('@'):
224 224 ltype = "difflineat"
225 225 else:
226 226 ltype = "diffline"
227 227 yield tmpl(ltype,
228 228 line=l,
229 229 lineid="l%s" % lineno,
230 230 linenumber="% 8s" % lineno)
231 231
232 232 if files:
233 233 m = match.exact(repo.root, repo.getcwd(), files)
234 234 else:
235 235 m = match.always(repo.root, repo.getcwd())
236 236
237 237 diffopts = patch.diffopts(repo.ui, untrusted=True)
238 238 if basectx is None:
239 239 parents = ctx.parents()
240 240 node1 = parents and parents[0].node() or nullid
241 241 else:
242 242 node1 = basectx.node()
243 243 node2 = ctx.node()
244 244
245 245 block = []
246 246 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
247 247 if chunk.startswith('diff') and block:
248 248 blockno = blockcount.next()
249 249 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
250 250 lines=prettyprintlines(''.join(block), blockno))
251 251 block = []
252 252 if chunk.startswith('diff') and style != 'raw':
253 253 chunk = ''.join(chunk.splitlines(True)[1:])
254 254 block.append(chunk)
255 255 blockno = blockcount.next()
256 256 yield tmpl('diffblock', parity=parity.next(), blockno=blockno,
257 257 lines=prettyprintlines(''.join(block), blockno))
258 258
259 259 def compare(tmpl, context, leftlines, rightlines):
260 260 '''Generator function that provides side-by-side comparison data.'''
261 261
262 262 def compline(type, leftlineno, leftline, rightlineno, rightline):
263 263 lineid = leftlineno and ("l%s" % leftlineno) or ''
264 264 lineid += rightlineno and ("r%s" % rightlineno) or ''
265 265 return tmpl('comparisonline',
266 266 type=type,
267 267 lineid=lineid,
268 268 leftlinenumber="% 6s" % (leftlineno or ''),
269 269 leftline=leftline or '',
270 270 rightlinenumber="% 6s" % (rightlineno or ''),
271 271 rightline=rightline or '')
272 272
273 273 def getblock(opcodes):
274 274 for type, llo, lhi, rlo, rhi in opcodes:
275 275 len1 = lhi - llo
276 276 len2 = rhi - rlo
277 277 count = min(len1, len2)
278 278 for i in xrange(count):
279 279 yield compline(type=type,
280 280 leftlineno=llo + i + 1,
281 281 leftline=leftlines[llo + i],
282 282 rightlineno=rlo + i + 1,
283 283 rightline=rightlines[rlo + i])
284 284 if len1 > len2:
285 285 for i in xrange(llo + count, lhi):
286 286 yield compline(type=type,
287 287 leftlineno=i + 1,
288 288 leftline=leftlines[i],
289 289 rightlineno=None,
290 290 rightline=None)
291 291 elif len2 > len1:
292 292 for i in xrange(rlo + count, rhi):
293 293 yield compline(type=type,
294 294 leftlineno=None,
295 295 leftline=None,
296 296 rightlineno=i + 1,
297 297 rightline=rightlines[i])
298 298
299 299 s = difflib.SequenceMatcher(None, leftlines, rightlines)
300 300 if context < 0:
301 301 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
302 302 else:
303 303 for oc in s.get_grouped_opcodes(n=context):
304 304 yield tmpl('comparisonblock', lines=getblock(oc))
305 305
306 306 def diffstatgen(ctx, basectx):
307 307 '''Generator function that provides the diffstat data.'''
308 308
309 309 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
310 310 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
311 311 while True:
312 312 yield stats, maxname, maxtotal, addtotal, removetotal, binary
313 313
314 314 def diffsummary(statgen):
315 315 '''Return a short summary of the diff.'''
316 316
317 317 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
318 318 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
319 319 len(stats), addtotal, removetotal)
320 320
321 321 def diffstat(tmpl, ctx, statgen, parity):
322 322 '''Return a diffstat template for each file in the diff.'''
323 323
324 324 stats, maxname, maxtotal, addtotal, removetotal, binary = statgen.next()
325 325 files = ctx.files()
326 326
327 327 def pct(i):
328 328 if maxtotal == 0:
329 329 return 0
330 330 return (float(i) / maxtotal) * 100
331 331
332 332 fileno = 0
333 333 for filename, adds, removes, isbinary in stats:
334 334 template = filename in files and 'diffstatlink' or 'diffstatnolink'
335 335 total = adds + removes
336 336 fileno += 1
337 337 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
338 338 total=total, addpct=pct(adds), removepct=pct(removes),
339 339 parity=parity.next())
340 340
341 341 class sessionvars(object):
342 342 def __init__(self, vars, start='?'):
343 343 self.start = start
344 344 self.vars = vars
345 345 def __getitem__(self, key):
346 346 return self.vars[key]
347 347 def __setitem__(self, key, value):
348 348 self.vars[key] = value
349 349 def __copy__(self):
350 350 return sessionvars(copy.copy(self.vars), self.start)
351 351 def __iter__(self):
352 352 separator = self.start
353 353 for key, value in sorted(self.vars.iteritems()):
354 354 yield {'name': key, 'value': str(value), 'separator': separator}
355 355 separator = '&'
356 356
357 357 class wsgiui(ui.ui):
358 358 # default termwidth breaks under mod_wsgi
359 359 def termwidth(self):
360 360 return 80
General Comments 0
You need to be logged in to leave comments. Login now