##// END OF EJS Templates
hgweb: use patch.diffhunks in webutil.diffs to simplify the algorithm...
Denis Laxalde -
r31276:cd29673c default
parent child Browse files
Show More
@@ -1,606 +1,596 b''
1 # hgweb/webutil.py - utility library for the web interface.
1 # hgweb/webutil.py - utility library for the web interface.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 from __future__ import absolute_import
9 from __future__ import absolute_import
10
10
11 import copy
11 import copy
12 import difflib
12 import difflib
13 import os
13 import os
14 import re
14 import re
15
15
16 from ..i18n import _
16 from ..i18n import _
17 from ..node import hex, nullid, short
17 from ..node import hex, nullid, short
18
18
19 from .common import (
19 from .common import (
20 ErrorResponse,
20 ErrorResponse,
21 HTTP_NOT_FOUND,
21 HTTP_NOT_FOUND,
22 paritygen,
22 paritygen,
23 )
23 )
24
24
25 from .. import (
25 from .. import (
26 context,
26 context,
27 error,
27 error,
28 match,
28 match,
29 patch,
29 patch,
30 pathutil,
30 pathutil,
31 templatefilters,
31 templatefilters,
32 ui as uimod,
32 ui as uimod,
33 util,
33 util,
34 )
34 )
35
35
36 def up(p):
36 def up(p):
37 if p[0] != "/":
37 if p[0] != "/":
38 p = "/" + p
38 p = "/" + p
39 if p[-1] == "/":
39 if p[-1] == "/":
40 p = p[:-1]
40 p = p[:-1]
41 up = os.path.dirname(p)
41 up = os.path.dirname(p)
42 if up == "/":
42 if up == "/":
43 return "/"
43 return "/"
44 return up + "/"
44 return up + "/"
45
45
46 def _navseq(step, firststep=None):
46 def _navseq(step, firststep=None):
47 if firststep:
47 if firststep:
48 yield firststep
48 yield firststep
49 if firststep >= 20 and firststep <= 40:
49 if firststep >= 20 and firststep <= 40:
50 firststep = 50
50 firststep = 50
51 yield firststep
51 yield firststep
52 assert step > 0
52 assert step > 0
53 assert firststep > 0
53 assert firststep > 0
54 while step <= firststep:
54 while step <= firststep:
55 step *= 10
55 step *= 10
56 while True:
56 while True:
57 yield 1 * step
57 yield 1 * step
58 yield 3 * step
58 yield 3 * step
59 step *= 10
59 step *= 10
60
60
61 class revnav(object):
61 class revnav(object):
62
62
63 def __init__(self, repo):
63 def __init__(self, repo):
64 """Navigation generation object
64 """Navigation generation object
65
65
66 :repo: repo object we generate nav for
66 :repo: repo object we generate nav for
67 """
67 """
68 # used for hex generation
68 # used for hex generation
69 self._revlog = repo.changelog
69 self._revlog = repo.changelog
70
70
71 def __nonzero__(self):
71 def __nonzero__(self):
72 """return True if any revision to navigate over"""
72 """return True if any revision to navigate over"""
73 return self._first() is not None
73 return self._first() is not None
74
74
75 def _first(self):
75 def _first(self):
76 """return the minimum non-filtered changeset or None"""
76 """return the minimum non-filtered changeset or None"""
77 try:
77 try:
78 return next(iter(self._revlog))
78 return next(iter(self._revlog))
79 except StopIteration:
79 except StopIteration:
80 return None
80 return None
81
81
82 def hex(self, rev):
82 def hex(self, rev):
83 return hex(self._revlog.node(rev))
83 return hex(self._revlog.node(rev))
84
84
85 def gen(self, pos, pagelen, limit):
85 def gen(self, pos, pagelen, limit):
86 """computes label and revision id for navigation link
86 """computes label and revision id for navigation link
87
87
88 :pos: is the revision relative to which we generate navigation.
88 :pos: is the revision relative to which we generate navigation.
89 :pagelen: the size of each navigation page
89 :pagelen: the size of each navigation page
90 :limit: how far shall we link
90 :limit: how far shall we link
91
91
92 The return is:
92 The return is:
93 - a single element tuple
93 - a single element tuple
94 - containing a dictionary with a `before` and `after` key
94 - containing a dictionary with a `before` and `after` key
95 - values are generator functions taking arbitrary number of kwargs
95 - values are generator functions taking arbitrary number of kwargs
96 - yield items are dictionaries with `label` and `node` keys
96 - yield items are dictionaries with `label` and `node` keys
97 """
97 """
98 if not self:
98 if not self:
99 # empty repo
99 # empty repo
100 return ({'before': (), 'after': ()},)
100 return ({'before': (), 'after': ()},)
101
101
102 targets = []
102 targets = []
103 for f in _navseq(1, pagelen):
103 for f in _navseq(1, pagelen):
104 if f > limit:
104 if f > limit:
105 break
105 break
106 targets.append(pos + f)
106 targets.append(pos + f)
107 targets.append(pos - f)
107 targets.append(pos - f)
108 targets.sort()
108 targets.sort()
109
109
110 first = self._first()
110 first = self._first()
111 navbefore = [("(%i)" % first, self.hex(first))]
111 navbefore = [("(%i)" % first, self.hex(first))]
112 navafter = []
112 navafter = []
113 for rev in targets:
113 for rev in targets:
114 if rev not in self._revlog:
114 if rev not in self._revlog:
115 continue
115 continue
116 if pos < rev < limit:
116 if pos < rev < limit:
117 navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
117 navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
118 if 0 < rev < pos:
118 if 0 < rev < pos:
119 navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
119 navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
120
120
121
121
122 navafter.append(("tip", "tip"))
122 navafter.append(("tip", "tip"))
123
123
124 data = lambda i: {"label": i[0], "node": i[1]}
124 data = lambda i: {"label": i[0], "node": i[1]}
125 return ({'before': lambda **map: (data(i) for i in navbefore),
125 return ({'before': lambda **map: (data(i) for i in navbefore),
126 'after': lambda **map: (data(i) for i in navafter)},)
126 'after': lambda **map: (data(i) for i in navafter)},)
127
127
128 class filerevnav(revnav):
128 class filerevnav(revnav):
129
129
130 def __init__(self, repo, path):
130 def __init__(self, repo, path):
131 """Navigation generation object
131 """Navigation generation object
132
132
133 :repo: repo object we generate nav for
133 :repo: repo object we generate nav for
134 :path: path of the file we generate nav for
134 :path: path of the file we generate nav for
135 """
135 """
136 # used for iteration
136 # used for iteration
137 self._changelog = repo.unfiltered().changelog
137 self._changelog = repo.unfiltered().changelog
138 # used for hex generation
138 # used for hex generation
139 self._revlog = repo.file(path)
139 self._revlog = repo.file(path)
140
140
141 def hex(self, rev):
141 def hex(self, rev):
142 return hex(self._changelog.node(self._revlog.linkrev(rev)))
142 return hex(self._changelog.node(self._revlog.linkrev(rev)))
143
143
144 class _siblings(object):
144 class _siblings(object):
145 def __init__(self, siblings=[], hiderev=None):
145 def __init__(self, siblings=[], hiderev=None):
146 self.siblings = [s for s in siblings if s.node() != nullid]
146 self.siblings = [s for s in siblings if s.node() != nullid]
147 if len(self.siblings) == 1 and self.siblings[0].rev() == hiderev:
147 if len(self.siblings) == 1 and self.siblings[0].rev() == hiderev:
148 self.siblings = []
148 self.siblings = []
149
149
150 def __iter__(self):
150 def __iter__(self):
151 for s in self.siblings:
151 for s in self.siblings:
152 d = {
152 d = {
153 'node': s.hex(),
153 'node': s.hex(),
154 'rev': s.rev(),
154 'rev': s.rev(),
155 'user': s.user(),
155 'user': s.user(),
156 'date': s.date(),
156 'date': s.date(),
157 'description': s.description(),
157 'description': s.description(),
158 'branch': s.branch(),
158 'branch': s.branch(),
159 }
159 }
160 if util.safehasattr(s, 'path'):
160 if util.safehasattr(s, 'path'):
161 d['file'] = s.path()
161 d['file'] = s.path()
162 yield d
162 yield d
163
163
164 def __len__(self):
164 def __len__(self):
165 return len(self.siblings)
165 return len(self.siblings)
166
166
167 def annotate(fctx, ui):
167 def annotate(fctx, ui):
168 diffopts = patch.difffeatureopts(ui, untrusted=True,
168 diffopts = patch.difffeatureopts(ui, untrusted=True,
169 section='annotate', whitespace=True)
169 section='annotate', whitespace=True)
170 return fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)
170 return fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)
171
171
172 def parents(ctx, hide=None):
172 def parents(ctx, hide=None):
173 if isinstance(ctx, context.basefilectx):
173 if isinstance(ctx, context.basefilectx):
174 introrev = ctx.introrev()
174 introrev = ctx.introrev()
175 if ctx.changectx().rev() != introrev:
175 if ctx.changectx().rev() != introrev:
176 return _siblings([ctx.repo()[introrev]], hide)
176 return _siblings([ctx.repo()[introrev]], hide)
177 return _siblings(ctx.parents(), hide)
177 return _siblings(ctx.parents(), hide)
178
178
179 def children(ctx, hide=None):
179 def children(ctx, hide=None):
180 return _siblings(ctx.children(), hide)
180 return _siblings(ctx.children(), hide)
181
181
182 def renamelink(fctx):
182 def renamelink(fctx):
183 r = fctx.renamed()
183 r = fctx.renamed()
184 if r:
184 if r:
185 return [{'file': r[0], 'node': hex(r[1])}]
185 return [{'file': r[0], 'node': hex(r[1])}]
186 return []
186 return []
187
187
188 def nodetagsdict(repo, node):
188 def nodetagsdict(repo, node):
189 return [{"name": i} for i in repo.nodetags(node)]
189 return [{"name": i} for i in repo.nodetags(node)]
190
190
191 def nodebookmarksdict(repo, node):
191 def nodebookmarksdict(repo, node):
192 return [{"name": i} for i in repo.nodebookmarks(node)]
192 return [{"name": i} for i in repo.nodebookmarks(node)]
193
193
194 def nodebranchdict(repo, ctx):
194 def nodebranchdict(repo, ctx):
195 branches = []
195 branches = []
196 branch = ctx.branch()
196 branch = ctx.branch()
197 # If this is an empty repo, ctx.node() == nullid,
197 # If this is an empty repo, ctx.node() == nullid,
198 # ctx.branch() == 'default'.
198 # ctx.branch() == 'default'.
199 try:
199 try:
200 branchnode = repo.branchtip(branch)
200 branchnode = repo.branchtip(branch)
201 except error.RepoLookupError:
201 except error.RepoLookupError:
202 branchnode = None
202 branchnode = None
203 if branchnode == ctx.node():
203 if branchnode == ctx.node():
204 branches.append({"name": branch})
204 branches.append({"name": branch})
205 return branches
205 return branches
206
206
207 def nodeinbranch(repo, ctx):
207 def nodeinbranch(repo, ctx):
208 branches = []
208 branches = []
209 branch = ctx.branch()
209 branch = ctx.branch()
210 try:
210 try:
211 branchnode = repo.branchtip(branch)
211 branchnode = repo.branchtip(branch)
212 except error.RepoLookupError:
212 except error.RepoLookupError:
213 branchnode = None
213 branchnode = None
214 if branch != 'default' and branchnode != ctx.node():
214 if branch != 'default' and branchnode != ctx.node():
215 branches.append({"name": branch})
215 branches.append({"name": branch})
216 return branches
216 return branches
217
217
218 def nodebranchnodefault(ctx):
218 def nodebranchnodefault(ctx):
219 branches = []
219 branches = []
220 branch = ctx.branch()
220 branch = ctx.branch()
221 if branch != 'default':
221 if branch != 'default':
222 branches.append({"name": branch})
222 branches.append({"name": branch})
223 return branches
223 return branches
224
224
225 def showtag(repo, tmpl, t1, node=nullid, **args):
225 def showtag(repo, tmpl, t1, node=nullid, **args):
226 for t in repo.nodetags(node):
226 for t in repo.nodetags(node):
227 yield tmpl(t1, tag=t, **args)
227 yield tmpl(t1, tag=t, **args)
228
228
229 def showbookmark(repo, tmpl, t1, node=nullid, **args):
229 def showbookmark(repo, tmpl, t1, node=nullid, **args):
230 for t in repo.nodebookmarks(node):
230 for t in repo.nodebookmarks(node):
231 yield tmpl(t1, bookmark=t, **args)
231 yield tmpl(t1, bookmark=t, **args)
232
232
233 def branchentries(repo, stripecount, limit=0):
233 def branchentries(repo, stripecount, limit=0):
234 tips = []
234 tips = []
235 heads = repo.heads()
235 heads = repo.heads()
236 parity = paritygen(stripecount)
236 parity = paritygen(stripecount)
237 sortkey = lambda item: (not item[1], item[0].rev())
237 sortkey = lambda item: (not item[1], item[0].rev())
238
238
239 def entries(**map):
239 def entries(**map):
240 count = 0
240 count = 0
241 if not tips:
241 if not tips:
242 for tag, hs, tip, closed in repo.branchmap().iterbranches():
242 for tag, hs, tip, closed in repo.branchmap().iterbranches():
243 tips.append((repo[tip], closed))
243 tips.append((repo[tip], closed))
244 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
244 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
245 if limit > 0 and count >= limit:
245 if limit > 0 and count >= limit:
246 return
246 return
247 count += 1
247 count += 1
248 if closed:
248 if closed:
249 status = 'closed'
249 status = 'closed'
250 elif ctx.node() not in heads:
250 elif ctx.node() not in heads:
251 status = 'inactive'
251 status = 'inactive'
252 else:
252 else:
253 status = 'open'
253 status = 'open'
254 yield {
254 yield {
255 'parity': next(parity),
255 'parity': next(parity),
256 'branch': ctx.branch(),
256 'branch': ctx.branch(),
257 'status': status,
257 'status': status,
258 'node': ctx.hex(),
258 'node': ctx.hex(),
259 'date': ctx.date()
259 'date': ctx.date()
260 }
260 }
261
261
262 return entries
262 return entries
263
263
264 def cleanpath(repo, path):
264 def cleanpath(repo, path):
265 path = path.lstrip('/')
265 path = path.lstrip('/')
266 return pathutil.canonpath(repo.root, '', path)
266 return pathutil.canonpath(repo.root, '', path)
267
267
268 def changeidctx(repo, changeid):
268 def changeidctx(repo, changeid):
269 try:
269 try:
270 ctx = repo[changeid]
270 ctx = repo[changeid]
271 except error.RepoError:
271 except error.RepoError:
272 man = repo.manifestlog._revlog
272 man = repo.manifestlog._revlog
273 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
273 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
274
274
275 return ctx
275 return ctx
276
276
277 def changectx(repo, req):
277 def changectx(repo, req):
278 changeid = "tip"
278 changeid = "tip"
279 if 'node' in req.form:
279 if 'node' in req.form:
280 changeid = req.form['node'][0]
280 changeid = req.form['node'][0]
281 ipos = changeid.find(':')
281 ipos = changeid.find(':')
282 if ipos != -1:
282 if ipos != -1:
283 changeid = changeid[(ipos + 1):]
283 changeid = changeid[(ipos + 1):]
284 elif 'manifest' in req.form:
284 elif 'manifest' in req.form:
285 changeid = req.form['manifest'][0]
285 changeid = req.form['manifest'][0]
286
286
287 return changeidctx(repo, changeid)
287 return changeidctx(repo, changeid)
288
288
289 def basechangectx(repo, req):
289 def basechangectx(repo, req):
290 if 'node' in req.form:
290 if 'node' in req.form:
291 changeid = req.form['node'][0]
291 changeid = req.form['node'][0]
292 ipos = changeid.find(':')
292 ipos = changeid.find(':')
293 if ipos != -1:
293 if ipos != -1:
294 changeid = changeid[:ipos]
294 changeid = changeid[:ipos]
295 return changeidctx(repo, changeid)
295 return changeidctx(repo, changeid)
296
296
297 return None
297 return None
298
298
299 def filectx(repo, req):
299 def filectx(repo, req):
300 if 'file' not in req.form:
300 if 'file' not in req.form:
301 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
301 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
302 path = cleanpath(repo, req.form['file'][0])
302 path = cleanpath(repo, req.form['file'][0])
303 if 'node' in req.form:
303 if 'node' in req.form:
304 changeid = req.form['node'][0]
304 changeid = req.form['node'][0]
305 elif 'filenode' in req.form:
305 elif 'filenode' in req.form:
306 changeid = req.form['filenode'][0]
306 changeid = req.form['filenode'][0]
307 else:
307 else:
308 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
308 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
309 try:
309 try:
310 fctx = repo[changeid][path]
310 fctx = repo[changeid][path]
311 except error.RepoError:
311 except error.RepoError:
312 fctx = repo.filectx(path, fileid=changeid)
312 fctx = repo.filectx(path, fileid=changeid)
313
313
314 return fctx
314 return fctx
315
315
316 def commonentry(repo, ctx):
316 def commonentry(repo, ctx):
317 node = ctx.node()
317 node = ctx.node()
318 return {
318 return {
319 'rev': ctx.rev(),
319 'rev': ctx.rev(),
320 'node': hex(node),
320 'node': hex(node),
321 'author': ctx.user(),
321 'author': ctx.user(),
322 'desc': ctx.description(),
322 'desc': ctx.description(),
323 'date': ctx.date(),
323 'date': ctx.date(),
324 'extra': ctx.extra(),
324 'extra': ctx.extra(),
325 'phase': ctx.phasestr(),
325 'phase': ctx.phasestr(),
326 'branch': nodebranchnodefault(ctx),
326 'branch': nodebranchnodefault(ctx),
327 'inbranch': nodeinbranch(repo, ctx),
327 'inbranch': nodeinbranch(repo, ctx),
328 'branches': nodebranchdict(repo, ctx),
328 'branches': nodebranchdict(repo, ctx),
329 'tags': nodetagsdict(repo, node),
329 'tags': nodetagsdict(repo, node),
330 'bookmarks': nodebookmarksdict(repo, node),
330 'bookmarks': nodebookmarksdict(repo, node),
331 'parent': lambda **x: parents(ctx),
331 'parent': lambda **x: parents(ctx),
332 'child': lambda **x: children(ctx),
332 'child': lambda **x: children(ctx),
333 }
333 }
334
334
335 def changelistentry(web, ctx, tmpl):
335 def changelistentry(web, ctx, tmpl):
336 '''Obtain a dictionary to be used for entries in a changelist.
336 '''Obtain a dictionary to be used for entries in a changelist.
337
337
338 This function is called when producing items for the "entries" list passed
338 This function is called when producing items for the "entries" list passed
339 to the "shortlog" and "changelog" templates.
339 to the "shortlog" and "changelog" templates.
340 '''
340 '''
341 repo = web.repo
341 repo = web.repo
342 rev = ctx.rev()
342 rev = ctx.rev()
343 n = ctx.node()
343 n = ctx.node()
344 showtags = showtag(repo, tmpl, 'changelogtag', n)
344 showtags = showtag(repo, tmpl, 'changelogtag', n)
345 files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
345 files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
346
346
347 entry = commonentry(repo, ctx)
347 entry = commonentry(repo, ctx)
348 entry.update(
348 entry.update(
349 allparents=lambda **x: parents(ctx),
349 allparents=lambda **x: parents(ctx),
350 parent=lambda **x: parents(ctx, rev - 1),
350 parent=lambda **x: parents(ctx, rev - 1),
351 child=lambda **x: children(ctx, rev + 1),
351 child=lambda **x: children(ctx, rev + 1),
352 changelogtag=showtags,
352 changelogtag=showtags,
353 files=files,
353 files=files,
354 )
354 )
355 return entry
355 return entry
356
356
357 def symrevorshortnode(req, ctx):
357 def symrevorshortnode(req, ctx):
358 if 'node' in req.form:
358 if 'node' in req.form:
359 return templatefilters.revescape(req.form['node'][0])
359 return templatefilters.revescape(req.form['node'][0])
360 else:
360 else:
361 return short(ctx.node())
361 return short(ctx.node())
362
362
363 def changesetentry(web, req, tmpl, ctx):
363 def changesetentry(web, req, tmpl, ctx):
364 '''Obtain a dictionary to be used to render the "changeset" template.'''
364 '''Obtain a dictionary to be used to render the "changeset" template.'''
365
365
366 showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
366 showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
367 showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
367 showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
368 ctx.node())
368 ctx.node())
369 showbranch = nodebranchnodefault(ctx)
369 showbranch = nodebranchnodefault(ctx)
370
370
371 files = []
371 files = []
372 parity = paritygen(web.stripecount)
372 parity = paritygen(web.stripecount)
373 for blockno, f in enumerate(ctx.files()):
373 for blockno, f in enumerate(ctx.files()):
374 template = f in ctx and 'filenodelink' or 'filenolink'
374 template = f in ctx and 'filenodelink' or 'filenolink'
375 files.append(tmpl(template,
375 files.append(tmpl(template,
376 node=ctx.hex(), file=f, blockno=blockno + 1,
376 node=ctx.hex(), file=f, blockno=blockno + 1,
377 parity=next(parity)))
377 parity=next(parity)))
378
378
379 basectx = basechangectx(web.repo, req)
379 basectx = basechangectx(web.repo, req)
380 if basectx is None:
380 if basectx is None:
381 basectx = ctx.p1()
381 basectx = ctx.p1()
382
382
383 style = web.config('web', 'style', 'paper')
383 style = web.config('web', 'style', 'paper')
384 if 'style' in req.form:
384 if 'style' in req.form:
385 style = req.form['style'][0]
385 style = req.form['style'][0]
386
386
387 parity = paritygen(web.stripecount)
387 parity = paritygen(web.stripecount)
388 diff = diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
388 diff = diffs(web.repo, tmpl, ctx, basectx, None, parity, style)
389
389
390 parity = paritygen(web.stripecount)
390 parity = paritygen(web.stripecount)
391 diffstatsgen = diffstatgen(ctx, basectx)
391 diffstatsgen = diffstatgen(ctx, basectx)
392 diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
392 diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
393
393
394 return dict(
394 return dict(
395 diff=diff,
395 diff=diff,
396 symrev=symrevorshortnode(req, ctx),
396 symrev=symrevorshortnode(req, ctx),
397 basenode=basectx.hex(),
397 basenode=basectx.hex(),
398 changesettag=showtags,
398 changesettag=showtags,
399 changesetbookmark=showbookmarks,
399 changesetbookmark=showbookmarks,
400 changesetbranch=showbranch,
400 changesetbranch=showbranch,
401 files=files,
401 files=files,
402 diffsummary=lambda **x: diffsummary(diffstatsgen),
402 diffsummary=lambda **x: diffsummary(diffstatsgen),
403 diffstat=diffstats,
403 diffstat=diffstats,
404 archives=web.archivelist(ctx.hex()),
404 archives=web.archivelist(ctx.hex()),
405 **commonentry(web.repo, ctx))
405 **commonentry(web.repo, ctx))
406
406
407 def listfilediffs(tmpl, files, node, max):
407 def listfilediffs(tmpl, files, node, max):
408 for f in files[:max]:
408 for f in files[:max]:
409 yield tmpl('filedifflink', node=hex(node), file=f)
409 yield tmpl('filedifflink', node=hex(node), file=f)
410 if len(files) > max:
410 if len(files) > max:
411 yield tmpl('fileellipses')
411 yield tmpl('fileellipses')
412
412
413 def diffs(repo, tmpl, ctx, basectx, files, parity, style):
413 def diffs(repo, tmpl, ctx, basectx, files, parity, style):
414
414
415 def countgen():
415 def prettyprintlines(lines, blockno):
416 start = 1
416 for lineno, l in enumerate(lines, 1):
417 while True:
418 yield start
419 start += 1
420
421 blockcount = countgen()
422 def prettyprintlines(diff, blockno):
423 for lineno, l in enumerate(diff.splitlines(True), 1):
424 difflineno = "%d.%d" % (blockno, lineno)
417 difflineno = "%d.%d" % (blockno, lineno)
425 if l.startswith('+'):
418 if l.startswith('+'):
426 ltype = "difflineplus"
419 ltype = "difflineplus"
427 elif l.startswith('-'):
420 elif l.startswith('-'):
428 ltype = "difflineminus"
421 ltype = "difflineminus"
429 elif l.startswith('@'):
422 elif l.startswith('@'):
430 ltype = "difflineat"
423 ltype = "difflineat"
431 else:
424 else:
432 ltype = "diffline"
425 ltype = "diffline"
433 yield tmpl(ltype,
426 yield tmpl(ltype,
434 line=l,
427 line=l,
435 lineno=lineno,
428 lineno=lineno,
436 lineid="l%s" % difflineno,
429 lineid="l%s" % difflineno,
437 linenumber="% 8s" % difflineno)
430 linenumber="% 8s" % difflineno)
438
431
439 if files:
432 if files:
440 m = match.exact(repo.root, repo.getcwd(), files)
433 m = match.exact(repo.root, repo.getcwd(), files)
441 else:
434 else:
442 m = match.always(repo.root, repo.getcwd())
435 m = match.always(repo.root, repo.getcwd())
443
436
444 diffopts = patch.diffopts(repo.ui, untrusted=True)
437 diffopts = patch.diffopts(repo.ui, untrusted=True)
445 node1 = basectx.node()
438 node1 = basectx.node()
446 node2 = ctx.node()
439 node2 = ctx.node()
447
440
448 block = []
441 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
449 for chunk in patch.diff(repo, node1, node2, m, opts=diffopts):
442 for blockno, (header, hunks) in enumerate(diffhunks, 1):
450 if chunk.startswith('diff') and block:
443 if style != 'raw':
451 blockno = next(blockcount)
444 header = header[1:]
445 lines = [h + '\n' for h in header]
446 for hunkrange, hunklines in hunks:
447 lines.extend(hunklines)
448 if lines:
452 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
449 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
453 lines=prettyprintlines(''.join(block), blockno))
450 lines=prettyprintlines(lines, blockno))
454 block = []
455 if chunk.startswith('diff') and style != 'raw':
456 chunk = ''.join(chunk.splitlines(True)[1:])
457 block.append(chunk)
458 blockno = next(blockcount)
459 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
460 lines=prettyprintlines(''.join(block), blockno))
461
451
462 def compare(tmpl, context, leftlines, rightlines):
452 def compare(tmpl, context, leftlines, rightlines):
463 '''Generator function that provides side-by-side comparison data.'''
453 '''Generator function that provides side-by-side comparison data.'''
464
454
465 def compline(type, leftlineno, leftline, rightlineno, rightline):
455 def compline(type, leftlineno, leftline, rightlineno, rightline):
466 lineid = leftlineno and ("l%s" % leftlineno) or ''
456 lineid = leftlineno and ("l%s" % leftlineno) or ''
467 lineid += rightlineno and ("r%s" % rightlineno) or ''
457 lineid += rightlineno and ("r%s" % rightlineno) or ''
468 return tmpl('comparisonline',
458 return tmpl('comparisonline',
469 type=type,
459 type=type,
470 lineid=lineid,
460 lineid=lineid,
471 leftlineno=leftlineno,
461 leftlineno=leftlineno,
472 leftlinenumber="% 6s" % (leftlineno or ''),
462 leftlinenumber="% 6s" % (leftlineno or ''),
473 leftline=leftline or '',
463 leftline=leftline or '',
474 rightlineno=rightlineno,
464 rightlineno=rightlineno,
475 rightlinenumber="% 6s" % (rightlineno or ''),
465 rightlinenumber="% 6s" % (rightlineno or ''),
476 rightline=rightline or '')
466 rightline=rightline or '')
477
467
478 def getblock(opcodes):
468 def getblock(opcodes):
479 for type, llo, lhi, rlo, rhi in opcodes:
469 for type, llo, lhi, rlo, rhi in opcodes:
480 len1 = lhi - llo
470 len1 = lhi - llo
481 len2 = rhi - rlo
471 len2 = rhi - rlo
482 count = min(len1, len2)
472 count = min(len1, len2)
483 for i in xrange(count):
473 for i in xrange(count):
484 yield compline(type=type,
474 yield compline(type=type,
485 leftlineno=llo + i + 1,
475 leftlineno=llo + i + 1,
486 leftline=leftlines[llo + i],
476 leftline=leftlines[llo + i],
487 rightlineno=rlo + i + 1,
477 rightlineno=rlo + i + 1,
488 rightline=rightlines[rlo + i])
478 rightline=rightlines[rlo + i])
489 if len1 > len2:
479 if len1 > len2:
490 for i in xrange(llo + count, lhi):
480 for i in xrange(llo + count, lhi):
491 yield compline(type=type,
481 yield compline(type=type,
492 leftlineno=i + 1,
482 leftlineno=i + 1,
493 leftline=leftlines[i],
483 leftline=leftlines[i],
494 rightlineno=None,
484 rightlineno=None,
495 rightline=None)
485 rightline=None)
496 elif len2 > len1:
486 elif len2 > len1:
497 for i in xrange(rlo + count, rhi):
487 for i in xrange(rlo + count, rhi):
498 yield compline(type=type,
488 yield compline(type=type,
499 leftlineno=None,
489 leftlineno=None,
500 leftline=None,
490 leftline=None,
501 rightlineno=i + 1,
491 rightlineno=i + 1,
502 rightline=rightlines[i])
492 rightline=rightlines[i])
503
493
504 s = difflib.SequenceMatcher(None, leftlines, rightlines)
494 s = difflib.SequenceMatcher(None, leftlines, rightlines)
505 if context < 0:
495 if context < 0:
506 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
496 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
507 else:
497 else:
508 for oc in s.get_grouped_opcodes(n=context):
498 for oc in s.get_grouped_opcodes(n=context):
509 yield tmpl('comparisonblock', lines=getblock(oc))
499 yield tmpl('comparisonblock', lines=getblock(oc))
510
500
511 def diffstatgen(ctx, basectx):
501 def diffstatgen(ctx, basectx):
512 '''Generator function that provides the diffstat data.'''
502 '''Generator function that provides the diffstat data.'''
513
503
514 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
504 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
515 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
505 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
516 while True:
506 while True:
517 yield stats, maxname, maxtotal, addtotal, removetotal, binary
507 yield stats, maxname, maxtotal, addtotal, removetotal, binary
518
508
519 def diffsummary(statgen):
509 def diffsummary(statgen):
520 '''Return a short summary of the diff.'''
510 '''Return a short summary of the diff.'''
521
511
522 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
512 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
523 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
513 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
524 len(stats), addtotal, removetotal)
514 len(stats), addtotal, removetotal)
525
515
526 def diffstat(tmpl, ctx, statgen, parity):
516 def diffstat(tmpl, ctx, statgen, parity):
527 '''Return a diffstat template for each file in the diff.'''
517 '''Return a diffstat template for each file in the diff.'''
528
518
529 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
519 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
530 files = ctx.files()
520 files = ctx.files()
531
521
532 def pct(i):
522 def pct(i):
533 if maxtotal == 0:
523 if maxtotal == 0:
534 return 0
524 return 0
535 return (float(i) / maxtotal) * 100
525 return (float(i) / maxtotal) * 100
536
526
537 fileno = 0
527 fileno = 0
538 for filename, adds, removes, isbinary in stats:
528 for filename, adds, removes, isbinary in stats:
539 template = filename in files and 'diffstatlink' or 'diffstatnolink'
529 template = filename in files and 'diffstatlink' or 'diffstatnolink'
540 total = adds + removes
530 total = adds + removes
541 fileno += 1
531 fileno += 1
542 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
532 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
543 total=total, addpct=pct(adds), removepct=pct(removes),
533 total=total, addpct=pct(adds), removepct=pct(removes),
544 parity=next(parity))
534 parity=next(parity))
545
535
546 class sessionvars(object):
536 class sessionvars(object):
547 def __init__(self, vars, start='?'):
537 def __init__(self, vars, start='?'):
548 self.start = start
538 self.start = start
549 self.vars = vars
539 self.vars = vars
550 def __getitem__(self, key):
540 def __getitem__(self, key):
551 return self.vars[key]
541 return self.vars[key]
552 def __setitem__(self, key, value):
542 def __setitem__(self, key, value):
553 self.vars[key] = value
543 self.vars[key] = value
554 def __copy__(self):
544 def __copy__(self):
555 return sessionvars(copy.copy(self.vars), self.start)
545 return sessionvars(copy.copy(self.vars), self.start)
556 def __iter__(self):
546 def __iter__(self):
557 separator = self.start
547 separator = self.start
558 for key, value in sorted(self.vars.iteritems()):
548 for key, value in sorted(self.vars.iteritems()):
559 yield {'name': key, 'value': str(value), 'separator': separator}
549 yield {'name': key, 'value': str(value), 'separator': separator}
560 separator = '&'
550 separator = '&'
561
551
562 class wsgiui(uimod.ui):
552 class wsgiui(uimod.ui):
563 # default termwidth breaks under mod_wsgi
553 # default termwidth breaks under mod_wsgi
564 def termwidth(self):
554 def termwidth(self):
565 return 80
555 return 80
566
556
567 def getwebsubs(repo):
557 def getwebsubs(repo):
568 websubtable = []
558 websubtable = []
569 websubdefs = repo.ui.configitems('websub')
559 websubdefs = repo.ui.configitems('websub')
570 # we must maintain interhg backwards compatibility
560 # we must maintain interhg backwards compatibility
571 websubdefs += repo.ui.configitems('interhg')
561 websubdefs += repo.ui.configitems('interhg')
572 for key, pattern in websubdefs:
562 for key, pattern in websubdefs:
573 # grab the delimiter from the character after the "s"
563 # grab the delimiter from the character after the "s"
574 unesc = pattern[1]
564 unesc = pattern[1]
575 delim = re.escape(unesc)
565 delim = re.escape(unesc)
576
566
577 # identify portions of the pattern, taking care to avoid escaped
567 # identify portions of the pattern, taking care to avoid escaped
578 # delimiters. the replace format and flags are optional, but
568 # delimiters. the replace format and flags are optional, but
579 # delimiters are required.
569 # delimiters are required.
580 match = re.match(
570 match = re.match(
581 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
571 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
582 % (delim, delim, delim), pattern)
572 % (delim, delim, delim), pattern)
583 if not match:
573 if not match:
584 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
574 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
585 % (key, pattern))
575 % (key, pattern))
586 continue
576 continue
587
577
588 # we need to unescape the delimiter for regexp and format
578 # we need to unescape the delimiter for regexp and format
589 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
579 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
590 regexp = delim_re.sub(unesc, match.group(1))
580 regexp = delim_re.sub(unesc, match.group(1))
591 format = delim_re.sub(unesc, match.group(2))
581 format = delim_re.sub(unesc, match.group(2))
592
582
593 # the pattern allows for 6 regexp flags, so set them if necessary
583 # the pattern allows for 6 regexp flags, so set them if necessary
594 flagin = match.group(3)
584 flagin = match.group(3)
595 flags = 0
585 flags = 0
596 if flagin:
586 if flagin:
597 for flag in flagin.upper():
587 for flag in flagin.upper():
598 flags |= re.__dict__[flag]
588 flags |= re.__dict__[flag]
599
589
600 try:
590 try:
601 regexp = re.compile(regexp, flags)
591 regexp = re.compile(regexp, flags)
602 websubtable.append((regexp, format))
592 websubtable.append((regexp, format))
603 except re.error:
593 except re.error:
604 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
594 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
605 % (key, regexp))
595 % (key, regexp))
606 return websubtable
596 return websubtable
General Comments 0
You need to be logged in to leave comments. Login now