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