##// END OF EJS Templates
mdiff: add a hunkinrange helper function...
Denis Laxalde -
r31808:ca3b4a2b default
parent child Browse files
Show More
@@ -1,628 +1,628 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_BAD_REQUEST,
21 HTTP_BAD_REQUEST,
22 HTTP_NOT_FOUND,
22 HTTP_NOT_FOUND,
23 paritygen,
23 paritygen,
24 )
24 )
25
25
26 from .. import (
26 from .. import (
27 context,
27 context,
28 error,
28 error,
29 match,
29 match,
30 mdiff,
30 patch,
31 patch,
31 pathutil,
32 pathutil,
32 templatefilters,
33 templatefilters,
33 ui as uimod,
34 ui as uimod,
34 util,
35 util,
35 )
36 )
36
37
37 def up(p):
38 def up(p):
38 if p[0] != "/":
39 if p[0] != "/":
39 p = "/" + p
40 p = "/" + p
40 if p[-1] == "/":
41 if p[-1] == "/":
41 p = p[:-1]
42 p = p[:-1]
42 up = os.path.dirname(p)
43 up = os.path.dirname(p)
43 if up == "/":
44 if up == "/":
44 return "/"
45 return "/"
45 return up + "/"
46 return up + "/"
46
47
47 def _navseq(step, firststep=None):
48 def _navseq(step, firststep=None):
48 if firststep:
49 if firststep:
49 yield firststep
50 yield firststep
50 if firststep >= 20 and firststep <= 40:
51 if firststep >= 20 and firststep <= 40:
51 firststep = 50
52 firststep = 50
52 yield firststep
53 yield firststep
53 assert step > 0
54 assert step > 0
54 assert firststep > 0
55 assert firststep > 0
55 while step <= firststep:
56 while step <= firststep:
56 step *= 10
57 step *= 10
57 while True:
58 while True:
58 yield 1 * step
59 yield 1 * step
59 yield 3 * step
60 yield 3 * step
60 step *= 10
61 step *= 10
61
62
62 class revnav(object):
63 class revnav(object):
63
64
64 def __init__(self, repo):
65 def __init__(self, repo):
65 """Navigation generation object
66 """Navigation generation object
66
67
67 :repo: repo object we generate nav for
68 :repo: repo object we generate nav for
68 """
69 """
69 # used for hex generation
70 # used for hex generation
70 self._revlog = repo.changelog
71 self._revlog = repo.changelog
71
72
72 def __nonzero__(self):
73 def __nonzero__(self):
73 """return True if any revision to navigate over"""
74 """return True if any revision to navigate over"""
74 return self._first() is not None
75 return self._first() is not None
75
76
76 __bool__ = __nonzero__
77 __bool__ = __nonzero__
77
78
78 def _first(self):
79 def _first(self):
79 """return the minimum non-filtered changeset or None"""
80 """return the minimum non-filtered changeset or None"""
80 try:
81 try:
81 return next(iter(self._revlog))
82 return next(iter(self._revlog))
82 except StopIteration:
83 except StopIteration:
83 return None
84 return None
84
85
85 def hex(self, rev):
86 def hex(self, rev):
86 return hex(self._revlog.node(rev))
87 return hex(self._revlog.node(rev))
87
88
88 def gen(self, pos, pagelen, limit):
89 def gen(self, pos, pagelen, limit):
89 """computes label and revision id for navigation link
90 """computes label and revision id for navigation link
90
91
91 :pos: is the revision relative to which we generate navigation.
92 :pos: is the revision relative to which we generate navigation.
92 :pagelen: the size of each navigation page
93 :pagelen: the size of each navigation page
93 :limit: how far shall we link
94 :limit: how far shall we link
94
95
95 The return is:
96 The return is:
96 - a single element tuple
97 - a single element tuple
97 - containing a dictionary with a `before` and `after` key
98 - containing a dictionary with a `before` and `after` key
98 - values are generator functions taking arbitrary number of kwargs
99 - values are generator functions taking arbitrary number of kwargs
99 - yield items are dictionaries with `label` and `node` keys
100 - yield items are dictionaries with `label` and `node` keys
100 """
101 """
101 if not self:
102 if not self:
102 # empty repo
103 # empty repo
103 return ({'before': (), 'after': ()},)
104 return ({'before': (), 'after': ()},)
104
105
105 targets = []
106 targets = []
106 for f in _navseq(1, pagelen):
107 for f in _navseq(1, pagelen):
107 if f > limit:
108 if f > limit:
108 break
109 break
109 targets.append(pos + f)
110 targets.append(pos + f)
110 targets.append(pos - f)
111 targets.append(pos - f)
111 targets.sort()
112 targets.sort()
112
113
113 first = self._first()
114 first = self._first()
114 navbefore = [("(%i)" % first, self.hex(first))]
115 navbefore = [("(%i)" % first, self.hex(first))]
115 navafter = []
116 navafter = []
116 for rev in targets:
117 for rev in targets:
117 if rev not in self._revlog:
118 if rev not in self._revlog:
118 continue
119 continue
119 if pos < rev < limit:
120 if pos < rev < limit:
120 navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
121 navafter.append(("+%d" % abs(rev - pos), self.hex(rev)))
121 if 0 < rev < pos:
122 if 0 < rev < pos:
122 navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
123 navbefore.append(("-%d" % abs(rev - pos), self.hex(rev)))
123
124
124
125
125 navafter.append(("tip", "tip"))
126 navafter.append(("tip", "tip"))
126
127
127 data = lambda i: {"label": i[0], "node": i[1]}
128 data = lambda i: {"label": i[0], "node": i[1]}
128 return ({'before': lambda **map: (data(i) for i in navbefore),
129 return ({'before': lambda **map: (data(i) for i in navbefore),
129 'after': lambda **map: (data(i) for i in navafter)},)
130 'after': lambda **map: (data(i) for i in navafter)},)
130
131
131 class filerevnav(revnav):
132 class filerevnav(revnav):
132
133
133 def __init__(self, repo, path):
134 def __init__(self, repo, path):
134 """Navigation generation object
135 """Navigation generation object
135
136
136 :repo: repo object we generate nav for
137 :repo: repo object we generate nav for
137 :path: path of the file we generate nav for
138 :path: path of the file we generate nav for
138 """
139 """
139 # used for iteration
140 # used for iteration
140 self._changelog = repo.unfiltered().changelog
141 self._changelog = repo.unfiltered().changelog
141 # used for hex generation
142 # used for hex generation
142 self._revlog = repo.file(path)
143 self._revlog = repo.file(path)
143
144
144 def hex(self, rev):
145 def hex(self, rev):
145 return hex(self._changelog.node(self._revlog.linkrev(rev)))
146 return hex(self._changelog.node(self._revlog.linkrev(rev)))
146
147
147 class _siblings(object):
148 class _siblings(object):
148 def __init__(self, siblings=None, hiderev=None):
149 def __init__(self, siblings=None, hiderev=None):
149 if siblings is None:
150 if siblings is None:
150 siblings = []
151 siblings = []
151 self.siblings = [s for s in siblings if s.node() != nullid]
152 self.siblings = [s for s in siblings if s.node() != nullid]
152 if len(self.siblings) == 1 and self.siblings[0].rev() == hiderev:
153 if len(self.siblings) == 1 and self.siblings[0].rev() == hiderev:
153 self.siblings = []
154 self.siblings = []
154
155
155 def __iter__(self):
156 def __iter__(self):
156 for s in self.siblings:
157 for s in self.siblings:
157 d = {
158 d = {
158 'node': s.hex(),
159 'node': s.hex(),
159 'rev': s.rev(),
160 'rev': s.rev(),
160 'user': s.user(),
161 'user': s.user(),
161 'date': s.date(),
162 'date': s.date(),
162 'description': s.description(),
163 'description': s.description(),
163 'branch': s.branch(),
164 'branch': s.branch(),
164 }
165 }
165 if util.safehasattr(s, 'path'):
166 if util.safehasattr(s, 'path'):
166 d['file'] = s.path()
167 d['file'] = s.path()
167 yield d
168 yield d
168
169
169 def __len__(self):
170 def __len__(self):
170 return len(self.siblings)
171 return len(self.siblings)
171
172
172 def annotate(fctx, ui):
173 def annotate(fctx, ui):
173 diffopts = patch.difffeatureopts(ui, untrusted=True,
174 diffopts = patch.difffeatureopts(ui, untrusted=True,
174 section='annotate', whitespace=True)
175 section='annotate', whitespace=True)
175 return fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)
176 return fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)
176
177
177 def parents(ctx, hide=None):
178 def parents(ctx, hide=None):
178 if isinstance(ctx, context.basefilectx):
179 if isinstance(ctx, context.basefilectx):
179 introrev = ctx.introrev()
180 introrev = ctx.introrev()
180 if ctx.changectx().rev() != introrev:
181 if ctx.changectx().rev() != introrev:
181 return _siblings([ctx.repo()[introrev]], hide)
182 return _siblings([ctx.repo()[introrev]], hide)
182 return _siblings(ctx.parents(), hide)
183 return _siblings(ctx.parents(), hide)
183
184
184 def children(ctx, hide=None):
185 def children(ctx, hide=None):
185 return _siblings(ctx.children(), hide)
186 return _siblings(ctx.children(), hide)
186
187
187 def renamelink(fctx):
188 def renamelink(fctx):
188 r = fctx.renamed()
189 r = fctx.renamed()
189 if r:
190 if r:
190 return [{'file': r[0], 'node': hex(r[1])}]
191 return [{'file': r[0], 'node': hex(r[1])}]
191 return []
192 return []
192
193
193 def nodetagsdict(repo, node):
194 def nodetagsdict(repo, node):
194 return [{"name": i} for i in repo.nodetags(node)]
195 return [{"name": i} for i in repo.nodetags(node)]
195
196
196 def nodebookmarksdict(repo, node):
197 def nodebookmarksdict(repo, node):
197 return [{"name": i} for i in repo.nodebookmarks(node)]
198 return [{"name": i} for i in repo.nodebookmarks(node)]
198
199
199 def nodebranchdict(repo, ctx):
200 def nodebranchdict(repo, ctx):
200 branches = []
201 branches = []
201 branch = ctx.branch()
202 branch = ctx.branch()
202 # If this is an empty repo, ctx.node() == nullid,
203 # If this is an empty repo, ctx.node() == nullid,
203 # ctx.branch() == 'default'.
204 # ctx.branch() == 'default'.
204 try:
205 try:
205 branchnode = repo.branchtip(branch)
206 branchnode = repo.branchtip(branch)
206 except error.RepoLookupError:
207 except error.RepoLookupError:
207 branchnode = None
208 branchnode = None
208 if branchnode == ctx.node():
209 if branchnode == ctx.node():
209 branches.append({"name": branch})
210 branches.append({"name": branch})
210 return branches
211 return branches
211
212
212 def nodeinbranch(repo, ctx):
213 def nodeinbranch(repo, ctx):
213 branches = []
214 branches = []
214 branch = ctx.branch()
215 branch = ctx.branch()
215 try:
216 try:
216 branchnode = repo.branchtip(branch)
217 branchnode = repo.branchtip(branch)
217 except error.RepoLookupError:
218 except error.RepoLookupError:
218 branchnode = None
219 branchnode = None
219 if branch != 'default' and branchnode != ctx.node():
220 if branch != 'default' and branchnode != ctx.node():
220 branches.append({"name": branch})
221 branches.append({"name": branch})
221 return branches
222 return branches
222
223
223 def nodebranchnodefault(ctx):
224 def nodebranchnodefault(ctx):
224 branches = []
225 branches = []
225 branch = ctx.branch()
226 branch = ctx.branch()
226 if branch != 'default':
227 if branch != 'default':
227 branches.append({"name": branch})
228 branches.append({"name": branch})
228 return branches
229 return branches
229
230
230 def showtag(repo, tmpl, t1, node=nullid, **args):
231 def showtag(repo, tmpl, t1, node=nullid, **args):
231 for t in repo.nodetags(node):
232 for t in repo.nodetags(node):
232 yield tmpl(t1, tag=t, **args)
233 yield tmpl(t1, tag=t, **args)
233
234
234 def showbookmark(repo, tmpl, t1, node=nullid, **args):
235 def showbookmark(repo, tmpl, t1, node=nullid, **args):
235 for t in repo.nodebookmarks(node):
236 for t in repo.nodebookmarks(node):
236 yield tmpl(t1, bookmark=t, **args)
237 yield tmpl(t1, bookmark=t, **args)
237
238
238 def branchentries(repo, stripecount, limit=0):
239 def branchentries(repo, stripecount, limit=0):
239 tips = []
240 tips = []
240 heads = repo.heads()
241 heads = repo.heads()
241 parity = paritygen(stripecount)
242 parity = paritygen(stripecount)
242 sortkey = lambda item: (not item[1], item[0].rev())
243 sortkey = lambda item: (not item[1], item[0].rev())
243
244
244 def entries(**map):
245 def entries(**map):
245 count = 0
246 count = 0
246 if not tips:
247 if not tips:
247 for tag, hs, tip, closed in repo.branchmap().iterbranches():
248 for tag, hs, tip, closed in repo.branchmap().iterbranches():
248 tips.append((repo[tip], closed))
249 tips.append((repo[tip], closed))
249 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
250 for ctx, closed in sorted(tips, key=sortkey, reverse=True):
250 if limit > 0 and count >= limit:
251 if limit > 0 and count >= limit:
251 return
252 return
252 count += 1
253 count += 1
253 if closed:
254 if closed:
254 status = 'closed'
255 status = 'closed'
255 elif ctx.node() not in heads:
256 elif ctx.node() not in heads:
256 status = 'inactive'
257 status = 'inactive'
257 else:
258 else:
258 status = 'open'
259 status = 'open'
259 yield {
260 yield {
260 'parity': next(parity),
261 'parity': next(parity),
261 'branch': ctx.branch(),
262 'branch': ctx.branch(),
262 'status': status,
263 'status': status,
263 'node': ctx.hex(),
264 'node': ctx.hex(),
264 'date': ctx.date()
265 'date': ctx.date()
265 }
266 }
266
267
267 return entries
268 return entries
268
269
269 def cleanpath(repo, path):
270 def cleanpath(repo, path):
270 path = path.lstrip('/')
271 path = path.lstrip('/')
271 return pathutil.canonpath(repo.root, '', path)
272 return pathutil.canonpath(repo.root, '', path)
272
273
273 def changeidctx(repo, changeid):
274 def changeidctx(repo, changeid):
274 try:
275 try:
275 ctx = repo[changeid]
276 ctx = repo[changeid]
276 except error.RepoError:
277 except error.RepoError:
277 man = repo.manifestlog._revlog
278 man = repo.manifestlog._revlog
278 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
279 ctx = repo[man.linkrev(man.rev(man.lookup(changeid)))]
279
280
280 return ctx
281 return ctx
281
282
282 def changectx(repo, req):
283 def changectx(repo, req):
283 changeid = "tip"
284 changeid = "tip"
284 if 'node' in req.form:
285 if 'node' in req.form:
285 changeid = req.form['node'][0]
286 changeid = req.form['node'][0]
286 ipos = changeid.find(':')
287 ipos = changeid.find(':')
287 if ipos != -1:
288 if ipos != -1:
288 changeid = changeid[(ipos + 1):]
289 changeid = changeid[(ipos + 1):]
289 elif 'manifest' in req.form:
290 elif 'manifest' in req.form:
290 changeid = req.form['manifest'][0]
291 changeid = req.form['manifest'][0]
291
292
292 return changeidctx(repo, changeid)
293 return changeidctx(repo, changeid)
293
294
294 def basechangectx(repo, req):
295 def basechangectx(repo, req):
295 if 'node' in req.form:
296 if 'node' in req.form:
296 changeid = req.form['node'][0]
297 changeid = req.form['node'][0]
297 ipos = changeid.find(':')
298 ipos = changeid.find(':')
298 if ipos != -1:
299 if ipos != -1:
299 changeid = changeid[:ipos]
300 changeid = changeid[:ipos]
300 return changeidctx(repo, changeid)
301 return changeidctx(repo, changeid)
301
302
302 return None
303 return None
303
304
304 def filectx(repo, req):
305 def filectx(repo, req):
305 if 'file' not in req.form:
306 if 'file' not in req.form:
306 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
307 raise ErrorResponse(HTTP_NOT_FOUND, 'file not given')
307 path = cleanpath(repo, req.form['file'][0])
308 path = cleanpath(repo, req.form['file'][0])
308 if 'node' in req.form:
309 if 'node' in req.form:
309 changeid = req.form['node'][0]
310 changeid = req.form['node'][0]
310 elif 'filenode' in req.form:
311 elif 'filenode' in req.form:
311 changeid = req.form['filenode'][0]
312 changeid = req.form['filenode'][0]
312 else:
313 else:
313 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
314 raise ErrorResponse(HTTP_NOT_FOUND, 'node or filenode not given')
314 try:
315 try:
315 fctx = repo[changeid][path]
316 fctx = repo[changeid][path]
316 except error.RepoError:
317 except error.RepoError:
317 fctx = repo.filectx(path, fileid=changeid)
318 fctx = repo.filectx(path, fileid=changeid)
318
319
319 return fctx
320 return fctx
320
321
321 def linerange(req):
322 def linerange(req):
322 linerange = req.form.get('linerange')
323 linerange = req.form.get('linerange')
323 if linerange is None:
324 if linerange is None:
324 return None
325 return None
325 if len(linerange) > 1:
326 if len(linerange) > 1:
326 raise ErrorResponse(HTTP_BAD_REQUEST,
327 raise ErrorResponse(HTTP_BAD_REQUEST,
327 'redundant linerange parameter')
328 'redundant linerange parameter')
328 try:
329 try:
329 fromline, toline = map(int, linerange[0].split(':', 1))
330 fromline, toline = map(int, linerange[0].split(':', 1))
330 except ValueError:
331 except ValueError:
331 raise ErrorResponse(HTTP_BAD_REQUEST,
332 raise ErrorResponse(HTTP_BAD_REQUEST,
332 'invalid linerange parameter')
333 'invalid linerange parameter')
333 try:
334 try:
334 return util.processlinerange(fromline, toline)
335 return util.processlinerange(fromline, toline)
335 except error.ParseError as exc:
336 except error.ParseError as exc:
336 raise ErrorResponse(HTTP_BAD_REQUEST, str(exc))
337 raise ErrorResponse(HTTP_BAD_REQUEST, str(exc))
337
338
338 def formatlinerange(fromline, toline):
339 def formatlinerange(fromline, toline):
339 return '%d:%d' % (fromline + 1, toline)
340 return '%d:%d' % (fromline + 1, toline)
340
341
341 def commonentry(repo, ctx):
342 def commonentry(repo, ctx):
342 node = ctx.node()
343 node = ctx.node()
343 return {
344 return {
344 'rev': ctx.rev(),
345 'rev': ctx.rev(),
345 'node': hex(node),
346 'node': hex(node),
346 'author': ctx.user(),
347 'author': ctx.user(),
347 'desc': ctx.description(),
348 'desc': ctx.description(),
348 'date': ctx.date(),
349 'date': ctx.date(),
349 'extra': ctx.extra(),
350 'extra': ctx.extra(),
350 'phase': ctx.phasestr(),
351 'phase': ctx.phasestr(),
351 'branch': nodebranchnodefault(ctx),
352 'branch': nodebranchnodefault(ctx),
352 'inbranch': nodeinbranch(repo, ctx),
353 'inbranch': nodeinbranch(repo, ctx),
353 'branches': nodebranchdict(repo, ctx),
354 'branches': nodebranchdict(repo, ctx),
354 'tags': nodetagsdict(repo, node),
355 'tags': nodetagsdict(repo, node),
355 'bookmarks': nodebookmarksdict(repo, node),
356 'bookmarks': nodebookmarksdict(repo, node),
356 'parent': lambda **x: parents(ctx),
357 'parent': lambda **x: parents(ctx),
357 'child': lambda **x: children(ctx),
358 'child': lambda **x: children(ctx),
358 }
359 }
359
360
360 def changelistentry(web, ctx, tmpl):
361 def changelistentry(web, ctx, tmpl):
361 '''Obtain a dictionary to be used for entries in a changelist.
362 '''Obtain a dictionary to be used for entries in a changelist.
362
363
363 This function is called when producing items for the "entries" list passed
364 This function is called when producing items for the "entries" list passed
364 to the "shortlog" and "changelog" templates.
365 to the "shortlog" and "changelog" templates.
365 '''
366 '''
366 repo = web.repo
367 repo = web.repo
367 rev = ctx.rev()
368 rev = ctx.rev()
368 n = ctx.node()
369 n = ctx.node()
369 showtags = showtag(repo, tmpl, 'changelogtag', n)
370 showtags = showtag(repo, tmpl, 'changelogtag', n)
370 files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
371 files = listfilediffs(tmpl, ctx.files(), n, web.maxfiles)
371
372
372 entry = commonentry(repo, ctx)
373 entry = commonentry(repo, ctx)
373 entry.update(
374 entry.update(
374 allparents=lambda **x: parents(ctx),
375 allparents=lambda **x: parents(ctx),
375 parent=lambda **x: parents(ctx, rev - 1),
376 parent=lambda **x: parents(ctx, rev - 1),
376 child=lambda **x: children(ctx, rev + 1),
377 child=lambda **x: children(ctx, rev + 1),
377 changelogtag=showtags,
378 changelogtag=showtags,
378 files=files,
379 files=files,
379 )
380 )
380 return entry
381 return entry
381
382
382 def symrevorshortnode(req, ctx):
383 def symrevorshortnode(req, ctx):
383 if 'node' in req.form:
384 if 'node' in req.form:
384 return templatefilters.revescape(req.form['node'][0])
385 return templatefilters.revescape(req.form['node'][0])
385 else:
386 else:
386 return short(ctx.node())
387 return short(ctx.node())
387
388
388 def changesetentry(web, req, tmpl, ctx):
389 def changesetentry(web, req, tmpl, ctx):
389 '''Obtain a dictionary to be used to render the "changeset" template.'''
390 '''Obtain a dictionary to be used to render the "changeset" template.'''
390
391
391 showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
392 showtags = showtag(web.repo, tmpl, 'changesettag', ctx.node())
392 showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
393 showbookmarks = showbookmark(web.repo, tmpl, 'changesetbookmark',
393 ctx.node())
394 ctx.node())
394 showbranch = nodebranchnodefault(ctx)
395 showbranch = nodebranchnodefault(ctx)
395
396
396 files = []
397 files = []
397 parity = paritygen(web.stripecount)
398 parity = paritygen(web.stripecount)
398 for blockno, f in enumerate(ctx.files()):
399 for blockno, f in enumerate(ctx.files()):
399 template = f in ctx and 'filenodelink' or 'filenolink'
400 template = f in ctx and 'filenodelink' or 'filenolink'
400 files.append(tmpl(template,
401 files.append(tmpl(template,
401 node=ctx.hex(), file=f, blockno=blockno + 1,
402 node=ctx.hex(), file=f, blockno=blockno + 1,
402 parity=next(parity)))
403 parity=next(parity)))
403
404
404 basectx = basechangectx(web.repo, req)
405 basectx = basechangectx(web.repo, req)
405 if basectx is None:
406 if basectx is None:
406 basectx = ctx.p1()
407 basectx = ctx.p1()
407
408
408 style = web.config('web', 'style', 'paper')
409 style = web.config('web', 'style', 'paper')
409 if 'style' in req.form:
410 if 'style' in req.form:
410 style = req.form['style'][0]
411 style = req.form['style'][0]
411
412
412 diff = diffs(web, tmpl, ctx, basectx, None, style)
413 diff = diffs(web, tmpl, ctx, basectx, None, style)
413
414
414 parity = paritygen(web.stripecount)
415 parity = paritygen(web.stripecount)
415 diffstatsgen = diffstatgen(ctx, basectx)
416 diffstatsgen = diffstatgen(ctx, basectx)
416 diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
417 diffstats = diffstat(tmpl, ctx, diffstatsgen, parity)
417
418
418 return dict(
419 return dict(
419 diff=diff,
420 diff=diff,
420 symrev=symrevorshortnode(req, ctx),
421 symrev=symrevorshortnode(req, ctx),
421 basenode=basectx.hex(),
422 basenode=basectx.hex(),
422 changesettag=showtags,
423 changesettag=showtags,
423 changesetbookmark=showbookmarks,
424 changesetbookmark=showbookmarks,
424 changesetbranch=showbranch,
425 changesetbranch=showbranch,
425 files=files,
426 files=files,
426 diffsummary=lambda **x: diffsummary(diffstatsgen),
427 diffsummary=lambda **x: diffsummary(diffstatsgen),
427 diffstat=diffstats,
428 diffstat=diffstats,
428 archives=web.archivelist(ctx.hex()),
429 archives=web.archivelist(ctx.hex()),
429 **commonentry(web.repo, ctx))
430 **commonentry(web.repo, ctx))
430
431
431 def listfilediffs(tmpl, files, node, max):
432 def listfilediffs(tmpl, files, node, max):
432 for f in files[:max]:
433 for f in files[:max]:
433 yield tmpl('filedifflink', node=hex(node), file=f)
434 yield tmpl('filedifflink', node=hex(node), file=f)
434 if len(files) > max:
435 if len(files) > max:
435 yield tmpl('fileellipses')
436 yield tmpl('fileellipses')
436
437
437 def diffs(web, tmpl, ctx, basectx, files, style, linerange=None,
438 def diffs(web, tmpl, ctx, basectx, files, style, linerange=None,
438 lineidprefix=''):
439 lineidprefix=''):
439
440
440 def prettyprintlines(lines, blockno):
441 def prettyprintlines(lines, blockno):
441 for lineno, l in enumerate(lines, 1):
442 for lineno, l in enumerate(lines, 1):
442 difflineno = "%d.%d" % (blockno, lineno)
443 difflineno = "%d.%d" % (blockno, lineno)
443 if l.startswith('+'):
444 if l.startswith('+'):
444 ltype = "difflineplus"
445 ltype = "difflineplus"
445 elif l.startswith('-'):
446 elif l.startswith('-'):
446 ltype = "difflineminus"
447 ltype = "difflineminus"
447 elif l.startswith('@'):
448 elif l.startswith('@'):
448 ltype = "difflineat"
449 ltype = "difflineat"
449 else:
450 else:
450 ltype = "diffline"
451 ltype = "diffline"
451 yield tmpl(ltype,
452 yield tmpl(ltype,
452 line=l,
453 line=l,
453 lineno=lineno,
454 lineno=lineno,
454 lineid=lineidprefix + "l%s" % difflineno,
455 lineid=lineidprefix + "l%s" % difflineno,
455 linenumber="% 8s" % difflineno)
456 linenumber="% 8s" % difflineno)
456
457
457 repo = web.repo
458 repo = web.repo
458 if files:
459 if files:
459 m = match.exact(repo.root, repo.getcwd(), files)
460 m = match.exact(repo.root, repo.getcwd(), files)
460 else:
461 else:
461 m = match.always(repo.root, repo.getcwd())
462 m = match.always(repo.root, repo.getcwd())
462
463
463 diffopts = patch.diffopts(repo.ui, untrusted=True)
464 diffopts = patch.diffopts(repo.ui, untrusted=True)
464 node1 = basectx.node()
465 node1 = basectx.node()
465 node2 = ctx.node()
466 node2 = ctx.node()
466 parity = paritygen(web.stripecount)
467 parity = paritygen(web.stripecount)
467
468
468 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
469 diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
469 for blockno, (header, hunks) in enumerate(diffhunks, 1):
470 for blockno, (header, hunks) in enumerate(diffhunks, 1):
470 if style != 'raw':
471 if style != 'raw':
471 header = header[1:]
472 header = header[1:]
472 lines = [h + '\n' for h in header]
473 lines = [h + '\n' for h in header]
473 for hunkrange, hunklines in hunks:
474 for hunkrange, hunklines in hunks:
474 if linerange is not None and hunkrange is not None:
475 if linerange is not None and hunkrange is not None:
475 s1, l1, s2, l2 = hunkrange
476 s1, l1, s2, l2 = hunkrange
476 lb, ub = linerange
477 if not mdiff.hunkinrange((s2, l2), linerange):
477 if not (lb < s2 + l2 and ub > s2):
478 continue
478 continue
479 lines.extend(hunklines)
479 lines.extend(hunklines)
480 if lines:
480 if lines:
481 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
481 yield tmpl('diffblock', parity=next(parity), blockno=blockno,
482 lines=prettyprintlines(lines, blockno))
482 lines=prettyprintlines(lines, blockno))
483
483
484 def compare(tmpl, context, leftlines, rightlines):
484 def compare(tmpl, context, leftlines, rightlines):
485 '''Generator function that provides side-by-side comparison data.'''
485 '''Generator function that provides side-by-side comparison data.'''
486
486
487 def compline(type, leftlineno, leftline, rightlineno, rightline):
487 def compline(type, leftlineno, leftline, rightlineno, rightline):
488 lineid = leftlineno and ("l%s" % leftlineno) or ''
488 lineid = leftlineno and ("l%s" % leftlineno) or ''
489 lineid += rightlineno and ("r%s" % rightlineno) or ''
489 lineid += rightlineno and ("r%s" % rightlineno) or ''
490 return tmpl('comparisonline',
490 return tmpl('comparisonline',
491 type=type,
491 type=type,
492 lineid=lineid,
492 lineid=lineid,
493 leftlineno=leftlineno,
493 leftlineno=leftlineno,
494 leftlinenumber="% 6s" % (leftlineno or ''),
494 leftlinenumber="% 6s" % (leftlineno or ''),
495 leftline=leftline or '',
495 leftline=leftline or '',
496 rightlineno=rightlineno,
496 rightlineno=rightlineno,
497 rightlinenumber="% 6s" % (rightlineno or ''),
497 rightlinenumber="% 6s" % (rightlineno or ''),
498 rightline=rightline or '')
498 rightline=rightline or '')
499
499
500 def getblock(opcodes):
500 def getblock(opcodes):
501 for type, llo, lhi, rlo, rhi in opcodes:
501 for type, llo, lhi, rlo, rhi in opcodes:
502 len1 = lhi - llo
502 len1 = lhi - llo
503 len2 = rhi - rlo
503 len2 = rhi - rlo
504 count = min(len1, len2)
504 count = min(len1, len2)
505 for i in xrange(count):
505 for i in xrange(count):
506 yield compline(type=type,
506 yield compline(type=type,
507 leftlineno=llo + i + 1,
507 leftlineno=llo + i + 1,
508 leftline=leftlines[llo + i],
508 leftline=leftlines[llo + i],
509 rightlineno=rlo + i + 1,
509 rightlineno=rlo + i + 1,
510 rightline=rightlines[rlo + i])
510 rightline=rightlines[rlo + i])
511 if len1 > len2:
511 if len1 > len2:
512 for i in xrange(llo + count, lhi):
512 for i in xrange(llo + count, lhi):
513 yield compline(type=type,
513 yield compline(type=type,
514 leftlineno=i + 1,
514 leftlineno=i + 1,
515 leftline=leftlines[i],
515 leftline=leftlines[i],
516 rightlineno=None,
516 rightlineno=None,
517 rightline=None)
517 rightline=None)
518 elif len2 > len1:
518 elif len2 > len1:
519 for i in xrange(rlo + count, rhi):
519 for i in xrange(rlo + count, rhi):
520 yield compline(type=type,
520 yield compline(type=type,
521 leftlineno=None,
521 leftlineno=None,
522 leftline=None,
522 leftline=None,
523 rightlineno=i + 1,
523 rightlineno=i + 1,
524 rightline=rightlines[i])
524 rightline=rightlines[i])
525
525
526 s = difflib.SequenceMatcher(None, leftlines, rightlines)
526 s = difflib.SequenceMatcher(None, leftlines, rightlines)
527 if context < 0:
527 if context < 0:
528 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
528 yield tmpl('comparisonblock', lines=getblock(s.get_opcodes()))
529 else:
529 else:
530 for oc in s.get_grouped_opcodes(n=context):
530 for oc in s.get_grouped_opcodes(n=context):
531 yield tmpl('comparisonblock', lines=getblock(oc))
531 yield tmpl('comparisonblock', lines=getblock(oc))
532
532
533 def diffstatgen(ctx, basectx):
533 def diffstatgen(ctx, basectx):
534 '''Generator function that provides the diffstat data.'''
534 '''Generator function that provides the diffstat data.'''
535
535
536 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
536 stats = patch.diffstatdata(util.iterlines(ctx.diff(basectx)))
537 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
537 maxname, maxtotal, addtotal, removetotal, binary = patch.diffstatsum(stats)
538 while True:
538 while True:
539 yield stats, maxname, maxtotal, addtotal, removetotal, binary
539 yield stats, maxname, maxtotal, addtotal, removetotal, binary
540
540
541 def diffsummary(statgen):
541 def diffsummary(statgen):
542 '''Return a short summary of the diff.'''
542 '''Return a short summary of the diff.'''
543
543
544 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
544 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
545 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
545 return _(' %d files changed, %d insertions(+), %d deletions(-)\n') % (
546 len(stats), addtotal, removetotal)
546 len(stats), addtotal, removetotal)
547
547
548 def diffstat(tmpl, ctx, statgen, parity):
548 def diffstat(tmpl, ctx, statgen, parity):
549 '''Return a diffstat template for each file in the diff.'''
549 '''Return a diffstat template for each file in the diff.'''
550
550
551 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
551 stats, maxname, maxtotal, addtotal, removetotal, binary = next(statgen)
552 files = ctx.files()
552 files = ctx.files()
553
553
554 def pct(i):
554 def pct(i):
555 if maxtotal == 0:
555 if maxtotal == 0:
556 return 0
556 return 0
557 return (float(i) / maxtotal) * 100
557 return (float(i) / maxtotal) * 100
558
558
559 fileno = 0
559 fileno = 0
560 for filename, adds, removes, isbinary in stats:
560 for filename, adds, removes, isbinary in stats:
561 template = filename in files and 'diffstatlink' or 'diffstatnolink'
561 template = filename in files and 'diffstatlink' or 'diffstatnolink'
562 total = adds + removes
562 total = adds + removes
563 fileno += 1
563 fileno += 1
564 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
564 yield tmpl(template, node=ctx.hex(), file=filename, fileno=fileno,
565 total=total, addpct=pct(adds), removepct=pct(removes),
565 total=total, addpct=pct(adds), removepct=pct(removes),
566 parity=next(parity))
566 parity=next(parity))
567
567
568 class sessionvars(object):
568 class sessionvars(object):
569 def __init__(self, vars, start='?'):
569 def __init__(self, vars, start='?'):
570 self.start = start
570 self.start = start
571 self.vars = vars
571 self.vars = vars
572 def __getitem__(self, key):
572 def __getitem__(self, key):
573 return self.vars[key]
573 return self.vars[key]
574 def __setitem__(self, key, value):
574 def __setitem__(self, key, value):
575 self.vars[key] = value
575 self.vars[key] = value
576 def __copy__(self):
576 def __copy__(self):
577 return sessionvars(copy.copy(self.vars), self.start)
577 return sessionvars(copy.copy(self.vars), self.start)
578 def __iter__(self):
578 def __iter__(self):
579 separator = self.start
579 separator = self.start
580 for key, value in sorted(self.vars.iteritems()):
580 for key, value in sorted(self.vars.iteritems()):
581 yield {'name': key, 'value': str(value), 'separator': separator}
581 yield {'name': key, 'value': str(value), 'separator': separator}
582 separator = '&'
582 separator = '&'
583
583
584 class wsgiui(uimod.ui):
584 class wsgiui(uimod.ui):
585 # default termwidth breaks under mod_wsgi
585 # default termwidth breaks under mod_wsgi
586 def termwidth(self):
586 def termwidth(self):
587 return 80
587 return 80
588
588
589 def getwebsubs(repo):
589 def getwebsubs(repo):
590 websubtable = []
590 websubtable = []
591 websubdefs = repo.ui.configitems('websub')
591 websubdefs = repo.ui.configitems('websub')
592 # we must maintain interhg backwards compatibility
592 # we must maintain interhg backwards compatibility
593 websubdefs += repo.ui.configitems('interhg')
593 websubdefs += repo.ui.configitems('interhg')
594 for key, pattern in websubdefs:
594 for key, pattern in websubdefs:
595 # grab the delimiter from the character after the "s"
595 # grab the delimiter from the character after the "s"
596 unesc = pattern[1]
596 unesc = pattern[1]
597 delim = re.escape(unesc)
597 delim = re.escape(unesc)
598
598
599 # identify portions of the pattern, taking care to avoid escaped
599 # identify portions of the pattern, taking care to avoid escaped
600 # delimiters. the replace format and flags are optional, but
600 # delimiters. the replace format and flags are optional, but
601 # delimiters are required.
601 # delimiters are required.
602 match = re.match(
602 match = re.match(
603 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
603 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
604 % (delim, delim, delim), pattern)
604 % (delim, delim, delim), pattern)
605 if not match:
605 if not match:
606 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
606 repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
607 % (key, pattern))
607 % (key, pattern))
608 continue
608 continue
609
609
610 # we need to unescape the delimiter for regexp and format
610 # we need to unescape the delimiter for regexp and format
611 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
611 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
612 regexp = delim_re.sub(unesc, match.group(1))
612 regexp = delim_re.sub(unesc, match.group(1))
613 format = delim_re.sub(unesc, match.group(2))
613 format = delim_re.sub(unesc, match.group(2))
614
614
615 # the pattern allows for 6 regexp flags, so set them if necessary
615 # the pattern allows for 6 regexp flags, so set them if necessary
616 flagin = match.group(3)
616 flagin = match.group(3)
617 flags = 0
617 flags = 0
618 if flagin:
618 if flagin:
619 for flag in flagin.upper():
619 for flag in flagin.upper():
620 flags |= re.__dict__[flag]
620 flags |= re.__dict__[flag]
621
621
622 try:
622 try:
623 regexp = re.compile(regexp, flags)
623 regexp = re.compile(regexp, flags)
624 websubtable.append((regexp, format))
624 websubtable.append((regexp, format))
625 except re.error:
625 except re.error:
626 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
626 repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
627 % (key, regexp))
627 % (key, regexp))
628 return websubtable
628 return websubtable
@@ -1,459 +1,484 b''
1 # mdiff.py - diff and patch routines for mercurial
1 # mdiff.py - diff and patch routines for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11 import struct
11 import struct
12 import zlib
12 import zlib
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import (
15 from . import (
16 base85,
16 base85,
17 bdiff,
17 bdiff,
18 error,
18 error,
19 mpatch,
19 mpatch,
20 pycompat,
20 pycompat,
21 util,
21 util,
22 )
22 )
23
23
24 def splitnewlines(text):
24 def splitnewlines(text):
25 '''like str.splitlines, but only split on newlines.'''
25 '''like str.splitlines, but only split on newlines.'''
26 lines = [l + '\n' for l in text.split('\n')]
26 lines = [l + '\n' for l in text.split('\n')]
27 if lines:
27 if lines:
28 if lines[-1] == '\n':
28 if lines[-1] == '\n':
29 lines.pop()
29 lines.pop()
30 else:
30 else:
31 lines[-1] = lines[-1][:-1]
31 lines[-1] = lines[-1][:-1]
32 return lines
32 return lines
33
33
34 class diffopts(object):
34 class diffopts(object):
35 '''context is the number of context lines
35 '''context is the number of context lines
36 text treats all files as text
36 text treats all files as text
37 showfunc enables diff -p output
37 showfunc enables diff -p output
38 git enables the git extended patch format
38 git enables the git extended patch format
39 nodates removes dates from diff headers
39 nodates removes dates from diff headers
40 nobinary ignores binary files
40 nobinary ignores binary files
41 noprefix disables the 'a/' and 'b/' prefixes (ignored in plain mode)
41 noprefix disables the 'a/' and 'b/' prefixes (ignored in plain mode)
42 ignorews ignores all whitespace changes in the diff
42 ignorews ignores all whitespace changes in the diff
43 ignorewsamount ignores changes in the amount of whitespace
43 ignorewsamount ignores changes in the amount of whitespace
44 ignoreblanklines ignores changes whose lines are all blank
44 ignoreblanklines ignores changes whose lines are all blank
45 upgrade generates git diffs to avoid data loss
45 upgrade generates git diffs to avoid data loss
46 '''
46 '''
47
47
48 defaults = {
48 defaults = {
49 'context': 3,
49 'context': 3,
50 'text': False,
50 'text': False,
51 'showfunc': False,
51 'showfunc': False,
52 'git': False,
52 'git': False,
53 'nodates': False,
53 'nodates': False,
54 'nobinary': False,
54 'nobinary': False,
55 'noprefix': False,
55 'noprefix': False,
56 'index': 0,
56 'index': 0,
57 'ignorews': False,
57 'ignorews': False,
58 'ignorewsamount': False,
58 'ignorewsamount': False,
59 'ignoreblanklines': False,
59 'ignoreblanklines': False,
60 'upgrade': False,
60 'upgrade': False,
61 'showsimilarity': False,
61 'showsimilarity': False,
62 }
62 }
63
63
64 def __init__(self, **opts):
64 def __init__(self, **opts):
65 opts = pycompat.byteskwargs(opts)
65 opts = pycompat.byteskwargs(opts)
66 for k in self.defaults.keys():
66 for k in self.defaults.keys():
67 v = opts.get(k)
67 v = opts.get(k)
68 if v is None:
68 if v is None:
69 v = self.defaults[k]
69 v = self.defaults[k]
70 setattr(self, k, v)
70 setattr(self, k, v)
71
71
72 try:
72 try:
73 self.context = int(self.context)
73 self.context = int(self.context)
74 except ValueError:
74 except ValueError:
75 raise error.Abort(_('diff context lines count must be '
75 raise error.Abort(_('diff context lines count must be '
76 'an integer, not %r') % self.context)
76 'an integer, not %r') % self.context)
77
77
78 def copy(self, **kwargs):
78 def copy(self, **kwargs):
79 opts = dict((k, getattr(self, k)) for k in self.defaults)
79 opts = dict((k, getattr(self, k)) for k in self.defaults)
80 opts.update(kwargs)
80 opts.update(kwargs)
81 return diffopts(**opts)
81 return diffopts(**opts)
82
82
83 defaultopts = diffopts()
83 defaultopts = diffopts()
84
84
85 def wsclean(opts, text, blank=True):
85 def wsclean(opts, text, blank=True):
86 if opts.ignorews:
86 if opts.ignorews:
87 text = bdiff.fixws(text, 1)
87 text = bdiff.fixws(text, 1)
88 elif opts.ignorewsamount:
88 elif opts.ignorewsamount:
89 text = bdiff.fixws(text, 0)
89 text = bdiff.fixws(text, 0)
90 if blank and opts.ignoreblanklines:
90 if blank and opts.ignoreblanklines:
91 text = re.sub('\n+', '\n', text).strip('\n')
91 text = re.sub('\n+', '\n', text).strip('\n')
92 return text
92 return text
93
93
94 def splitblock(base1, lines1, base2, lines2, opts):
94 def splitblock(base1, lines1, base2, lines2, opts):
95 # The input lines matches except for interwoven blank lines. We
95 # The input lines matches except for interwoven blank lines. We
96 # transform it into a sequence of matching blocks and blank blocks.
96 # transform it into a sequence of matching blocks and blank blocks.
97 lines1 = [(wsclean(opts, l) and 1 or 0) for l in lines1]
97 lines1 = [(wsclean(opts, l) and 1 or 0) for l in lines1]
98 lines2 = [(wsclean(opts, l) and 1 or 0) for l in lines2]
98 lines2 = [(wsclean(opts, l) and 1 or 0) for l in lines2]
99 s1, e1 = 0, len(lines1)
99 s1, e1 = 0, len(lines1)
100 s2, e2 = 0, len(lines2)
100 s2, e2 = 0, len(lines2)
101 while s1 < e1 or s2 < e2:
101 while s1 < e1 or s2 < e2:
102 i1, i2, btype = s1, s2, '='
102 i1, i2, btype = s1, s2, '='
103 if (i1 >= e1 or lines1[i1] == 0
103 if (i1 >= e1 or lines1[i1] == 0
104 or i2 >= e2 or lines2[i2] == 0):
104 or i2 >= e2 or lines2[i2] == 0):
105 # Consume the block of blank lines
105 # Consume the block of blank lines
106 btype = '~'
106 btype = '~'
107 while i1 < e1 and lines1[i1] == 0:
107 while i1 < e1 and lines1[i1] == 0:
108 i1 += 1
108 i1 += 1
109 while i2 < e2 and lines2[i2] == 0:
109 while i2 < e2 and lines2[i2] == 0:
110 i2 += 1
110 i2 += 1
111 else:
111 else:
112 # Consume the matching lines
112 # Consume the matching lines
113 while i1 < e1 and lines1[i1] == 1 and lines2[i2] == 1:
113 while i1 < e1 and lines1[i1] == 1 and lines2[i2] == 1:
114 i1 += 1
114 i1 += 1
115 i2 += 1
115 i2 += 1
116 yield [base1 + s1, base1 + i1, base2 + s2, base2 + i2], btype
116 yield [base1 + s1, base1 + i1, base2 + s2, base2 + i2], btype
117 s1 = i1
117 s1 = i1
118 s2 = i2
118 s2 = i2
119
119
120 def hunkinrange(hunk, linerange):
121 """Return True if `hunk` defined as (start, length) is in `linerange`
122 defined as (lowerbound, upperbound).
123
124 >>> hunkinrange((5, 10), (2, 7))
125 True
126 >>> hunkinrange((5, 10), (6, 12))
127 True
128 >>> hunkinrange((5, 10), (13, 17))
129 True
130 >>> hunkinrange((5, 10), (3, 17))
131 True
132 >>> hunkinrange((5, 10), (1, 3))
133 False
134 >>> hunkinrange((5, 10), (18, 20))
135 False
136 >>> hunkinrange((5, 10), (1, 5))
137 False
138 >>> hunkinrange((5, 10), (15, 27))
139 False
140 """
141 start, length = hunk
142 lowerbound, upperbound = linerange
143 return lowerbound < start + length and start < upperbound
144
120 def blocksinrange(blocks, rangeb):
145 def blocksinrange(blocks, rangeb):
121 """filter `blocks` like (a1, a2, b1, b2) from items outside line range
146 """filter `blocks` like (a1, a2, b1, b2) from items outside line range
122 `rangeb` from ``(b1, b2)`` point of view.
147 `rangeb` from ``(b1, b2)`` point of view.
123
148
124 Return `filteredblocks, rangea` where:
149 Return `filteredblocks, rangea` where:
125
150
126 * `filteredblocks` is list of ``block = (a1, a2, b1, b2), stype`` items of
151 * `filteredblocks` is list of ``block = (a1, a2, b1, b2), stype`` items of
127 `blocks` that are inside `rangeb` from ``(b1, b2)`` point of view; a
152 `blocks` that are inside `rangeb` from ``(b1, b2)`` point of view; a
128 block ``(b1, b2)`` being inside `rangeb` if
153 block ``(b1, b2)`` being inside `rangeb` if
129 ``rangeb[0] < b2 and b1 < rangeb[1]``;
154 ``rangeb[0] < b2 and b1 < rangeb[1]``;
130 * `rangea` is the line range w.r.t. to ``(a1, a2)`` parts of `blocks`.
155 * `rangea` is the line range w.r.t. to ``(a1, a2)`` parts of `blocks`.
131 """
156 """
132 lbb, ubb = rangeb
157 lbb, ubb = rangeb
133 lba, uba = None, None
158 lba, uba = None, None
134 filteredblocks = []
159 filteredblocks = []
135 for block in blocks:
160 for block in blocks:
136 (a1, a2, b1, b2), stype = block
161 (a1, a2, b1, b2), stype = block
137 if lbb >= b1 and ubb <= b2 and stype == '=':
162 if lbb >= b1 and ubb <= b2 and stype == '=':
138 # rangeb is within a single "=" hunk, restrict back linerange1
163 # rangeb is within a single "=" hunk, restrict back linerange1
139 # by offsetting rangeb
164 # by offsetting rangeb
140 lba = lbb - b1 + a1
165 lba = lbb - b1 + a1
141 uba = ubb - b1 + a1
166 uba = ubb - b1 + a1
142 else:
167 else:
143 if b1 <= lbb < b2:
168 if b1 <= lbb < b2:
144 if stype == '=':
169 if stype == '=':
145 lba = a2 - (b2 - lbb)
170 lba = a2 - (b2 - lbb)
146 else:
171 else:
147 lba = a1
172 lba = a1
148 if b1 < ubb <= b2:
173 if b1 < ubb <= b2:
149 if stype == '=':
174 if stype == '=':
150 uba = a1 + (ubb - b1)
175 uba = a1 + (ubb - b1)
151 else:
176 else:
152 uba = a2
177 uba = a2
153 if lbb < b2 and b1 < ubb:
178 if hunkinrange((b1, (b2 - b1)), rangeb):
154 filteredblocks.append(block)
179 filteredblocks.append(block)
155 if lba is None or uba is None or uba < lba:
180 if lba is None or uba is None or uba < lba:
156 raise error.Abort(_('line range exceeds file size'))
181 raise error.Abort(_('line range exceeds file size'))
157 return filteredblocks, (lba, uba)
182 return filteredblocks, (lba, uba)
158
183
159 def allblocks(text1, text2, opts=None, lines1=None, lines2=None):
184 def allblocks(text1, text2, opts=None, lines1=None, lines2=None):
160 """Return (block, type) tuples, where block is an mdiff.blocks
185 """Return (block, type) tuples, where block is an mdiff.blocks
161 line entry. type is '=' for blocks matching exactly one another
186 line entry. type is '=' for blocks matching exactly one another
162 (bdiff blocks), '!' for non-matching blocks and '~' for blocks
187 (bdiff blocks), '!' for non-matching blocks and '~' for blocks
163 matching only after having filtered blank lines.
188 matching only after having filtered blank lines.
164 line1 and line2 are text1 and text2 split with splitnewlines() if
189 line1 and line2 are text1 and text2 split with splitnewlines() if
165 they are already available.
190 they are already available.
166 """
191 """
167 if opts is None:
192 if opts is None:
168 opts = defaultopts
193 opts = defaultopts
169 if opts.ignorews or opts.ignorewsamount:
194 if opts.ignorews or opts.ignorewsamount:
170 text1 = wsclean(opts, text1, False)
195 text1 = wsclean(opts, text1, False)
171 text2 = wsclean(opts, text2, False)
196 text2 = wsclean(opts, text2, False)
172 diff = bdiff.blocks(text1, text2)
197 diff = bdiff.blocks(text1, text2)
173 for i, s1 in enumerate(diff):
198 for i, s1 in enumerate(diff):
174 # The first match is special.
199 # The first match is special.
175 # we've either found a match starting at line 0 or a match later
200 # we've either found a match starting at line 0 or a match later
176 # in the file. If it starts later, old and new below will both be
201 # in the file. If it starts later, old and new below will both be
177 # empty and we'll continue to the next match.
202 # empty and we'll continue to the next match.
178 if i > 0:
203 if i > 0:
179 s = diff[i - 1]
204 s = diff[i - 1]
180 else:
205 else:
181 s = [0, 0, 0, 0]
206 s = [0, 0, 0, 0]
182 s = [s[1], s1[0], s[3], s1[2]]
207 s = [s[1], s1[0], s[3], s1[2]]
183
208
184 # bdiff sometimes gives huge matches past eof, this check eats them,
209 # bdiff sometimes gives huge matches past eof, this check eats them,
185 # and deals with the special first match case described above
210 # and deals with the special first match case described above
186 if s[0] != s[1] or s[2] != s[3]:
211 if s[0] != s[1] or s[2] != s[3]:
187 type = '!'
212 type = '!'
188 if opts.ignoreblanklines:
213 if opts.ignoreblanklines:
189 if lines1 is None:
214 if lines1 is None:
190 lines1 = splitnewlines(text1)
215 lines1 = splitnewlines(text1)
191 if lines2 is None:
216 if lines2 is None:
192 lines2 = splitnewlines(text2)
217 lines2 = splitnewlines(text2)
193 old = wsclean(opts, "".join(lines1[s[0]:s[1]]))
218 old = wsclean(opts, "".join(lines1[s[0]:s[1]]))
194 new = wsclean(opts, "".join(lines2[s[2]:s[3]]))
219 new = wsclean(opts, "".join(lines2[s[2]:s[3]]))
195 if old == new:
220 if old == new:
196 type = '~'
221 type = '~'
197 yield s, type
222 yield s, type
198 yield s1, '='
223 yield s1, '='
199
224
200 def unidiff(a, ad, b, bd, fn1, fn2, opts=defaultopts):
225 def unidiff(a, ad, b, bd, fn1, fn2, opts=defaultopts):
201 """Return a unified diff as a (headers, hunks) tuple.
226 """Return a unified diff as a (headers, hunks) tuple.
202
227
203 If the diff is not null, `headers` is a list with unified diff header
228 If the diff is not null, `headers` is a list with unified diff header
204 lines "--- <original>" and "+++ <new>" and `hunks` is a generator yielding
229 lines "--- <original>" and "+++ <new>" and `hunks` is a generator yielding
205 (hunkrange, hunklines) coming from _unidiff().
230 (hunkrange, hunklines) coming from _unidiff().
206 Otherwise, `headers` and `hunks` are empty.
231 Otherwise, `headers` and `hunks` are empty.
207 """
232 """
208 def datetag(date, fn=None):
233 def datetag(date, fn=None):
209 if not opts.git and not opts.nodates:
234 if not opts.git and not opts.nodates:
210 return '\t%s' % date
235 return '\t%s' % date
211 if fn and ' ' in fn:
236 if fn and ' ' in fn:
212 return '\t'
237 return '\t'
213 return ''
238 return ''
214
239
215 sentinel = [], ()
240 sentinel = [], ()
216 if not a and not b:
241 if not a and not b:
217 return sentinel
242 return sentinel
218
243
219 if opts.noprefix:
244 if opts.noprefix:
220 aprefix = bprefix = ''
245 aprefix = bprefix = ''
221 else:
246 else:
222 aprefix = 'a/'
247 aprefix = 'a/'
223 bprefix = 'b/'
248 bprefix = 'b/'
224
249
225 epoch = util.datestr((0, 0))
250 epoch = util.datestr((0, 0))
226
251
227 fn1 = util.pconvert(fn1)
252 fn1 = util.pconvert(fn1)
228 fn2 = util.pconvert(fn2)
253 fn2 = util.pconvert(fn2)
229
254
230 def checknonewline(lines):
255 def checknonewline(lines):
231 for text in lines:
256 for text in lines:
232 if text[-1:] != '\n':
257 if text[-1:] != '\n':
233 text += "\n\ No newline at end of file\n"
258 text += "\n\ No newline at end of file\n"
234 yield text
259 yield text
235
260
236 if not opts.text and (util.binary(a) or util.binary(b)):
261 if not opts.text and (util.binary(a) or util.binary(b)):
237 if a and b and len(a) == len(b) and a == b:
262 if a and b and len(a) == len(b) and a == b:
238 return sentinel
263 return sentinel
239 headerlines = []
264 headerlines = []
240 hunks = (None, ['Binary file %s has changed\n' % fn1]),
265 hunks = (None, ['Binary file %s has changed\n' % fn1]),
241 elif not a:
266 elif not a:
242 b = splitnewlines(b)
267 b = splitnewlines(b)
243 if a is None:
268 if a is None:
244 l1 = '--- /dev/null%s' % datetag(epoch)
269 l1 = '--- /dev/null%s' % datetag(epoch)
245 else:
270 else:
246 l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
271 l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
247 l2 = "+++ %s%s" % (bprefix + fn2, datetag(bd, fn2))
272 l2 = "+++ %s%s" % (bprefix + fn2, datetag(bd, fn2))
248 headerlines = [l1, l2]
273 headerlines = [l1, l2]
249 size = len(b)
274 size = len(b)
250 hunkrange = (0, 0, 1, size)
275 hunkrange = (0, 0, 1, size)
251 hunklines = ["@@ -0,0 +1,%d @@\n" % size] + ["+" + e for e in b]
276 hunklines = ["@@ -0,0 +1,%d @@\n" % size] + ["+" + e for e in b]
252 hunks = (hunkrange, checknonewline(hunklines)),
277 hunks = (hunkrange, checknonewline(hunklines)),
253 elif not b:
278 elif not b:
254 a = splitnewlines(a)
279 a = splitnewlines(a)
255 l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
280 l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
256 if b is None:
281 if b is None:
257 l2 = '+++ /dev/null%s' % datetag(epoch)
282 l2 = '+++ /dev/null%s' % datetag(epoch)
258 else:
283 else:
259 l2 = "+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2))
284 l2 = "+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2))
260 headerlines = [l1, l2]
285 headerlines = [l1, l2]
261 size = len(a)
286 size = len(a)
262 hunkrange = (1, size, 0, 0)
287 hunkrange = (1, size, 0, 0)
263 hunklines = ["@@ -1,%d +0,0 @@\n" % size] + ["-" + e for e in a]
288 hunklines = ["@@ -1,%d +0,0 @@\n" % size] + ["-" + e for e in a]
264 hunks = (hunkrange, checknonewline(hunklines)),
289 hunks = (hunkrange, checknonewline(hunklines)),
265 else:
290 else:
266 diffhunks = _unidiff(a, b, opts=opts)
291 diffhunks = _unidiff(a, b, opts=opts)
267 try:
292 try:
268 hunkrange, hunklines = next(diffhunks)
293 hunkrange, hunklines = next(diffhunks)
269 except StopIteration:
294 except StopIteration:
270 return sentinel
295 return sentinel
271
296
272 headerlines = [
297 headerlines = [
273 "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1)),
298 "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1)),
274 "+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2)),
299 "+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2)),
275 ]
300 ]
276 def rewindhunks():
301 def rewindhunks():
277 yield hunkrange, checknonewline(hunklines)
302 yield hunkrange, checknonewline(hunklines)
278 for hr, hl in diffhunks:
303 for hr, hl in diffhunks:
279 yield hr, checknonewline(hl)
304 yield hr, checknonewline(hl)
280
305
281 hunks = rewindhunks()
306 hunks = rewindhunks()
282
307
283 return headerlines, hunks
308 return headerlines, hunks
284
309
285 def _unidiff(t1, t2, opts=defaultopts):
310 def _unidiff(t1, t2, opts=defaultopts):
286 """Yield hunks of a headerless unified diff from t1 and t2 texts.
311 """Yield hunks of a headerless unified diff from t1 and t2 texts.
287
312
288 Each hunk consists of a (hunkrange, hunklines) tuple where `hunkrange` is a
313 Each hunk consists of a (hunkrange, hunklines) tuple where `hunkrange` is a
289 tuple (s1, l1, s2, l2) representing the range information of the hunk to
314 tuple (s1, l1, s2, l2) representing the range information of the hunk to
290 form the '@@ -s1,l1 +s2,l2 @@' header and `hunklines` is a list of lines
315 form the '@@ -s1,l1 +s2,l2 @@' header and `hunklines` is a list of lines
291 of the hunk combining said header followed by line additions and
316 of the hunk combining said header followed by line additions and
292 deletions.
317 deletions.
293 """
318 """
294 l1 = splitnewlines(t1)
319 l1 = splitnewlines(t1)
295 l2 = splitnewlines(t2)
320 l2 = splitnewlines(t2)
296 def contextend(l, len):
321 def contextend(l, len):
297 ret = l + opts.context
322 ret = l + opts.context
298 if ret > len:
323 if ret > len:
299 ret = len
324 ret = len
300 return ret
325 return ret
301
326
302 def contextstart(l):
327 def contextstart(l):
303 ret = l - opts.context
328 ret = l - opts.context
304 if ret < 0:
329 if ret < 0:
305 return 0
330 return 0
306 return ret
331 return ret
307
332
308 lastfunc = [0, '']
333 lastfunc = [0, '']
309 def yieldhunk(hunk):
334 def yieldhunk(hunk):
310 (astart, a2, bstart, b2, delta) = hunk
335 (astart, a2, bstart, b2, delta) = hunk
311 aend = contextend(a2, len(l1))
336 aend = contextend(a2, len(l1))
312 alen = aend - astart
337 alen = aend - astart
313 blen = b2 - bstart + aend - a2
338 blen = b2 - bstart + aend - a2
314
339
315 func = ""
340 func = ""
316 if opts.showfunc:
341 if opts.showfunc:
317 lastpos, func = lastfunc
342 lastpos, func = lastfunc
318 # walk backwards from the start of the context up to the start of
343 # walk backwards from the start of the context up to the start of
319 # the previous hunk context until we find a line starting with an
344 # the previous hunk context until we find a line starting with an
320 # alphanumeric char.
345 # alphanumeric char.
321 for i in xrange(astart - 1, lastpos - 1, -1):
346 for i in xrange(astart - 1, lastpos - 1, -1):
322 if l1[i][0].isalnum():
347 if l1[i][0].isalnum():
323 func = ' ' + l1[i].rstrip()[:40]
348 func = ' ' + l1[i].rstrip()[:40]
324 lastfunc[1] = func
349 lastfunc[1] = func
325 break
350 break
326 # by recording this hunk's starting point as the next place to
351 # by recording this hunk's starting point as the next place to
327 # start looking for function lines, we avoid reading any line in
352 # start looking for function lines, we avoid reading any line in
328 # the file more than once.
353 # the file more than once.
329 lastfunc[0] = astart
354 lastfunc[0] = astart
330
355
331 # zero-length hunk ranges report their start line as one less
356 # zero-length hunk ranges report their start line as one less
332 if alen:
357 if alen:
333 astart += 1
358 astart += 1
334 if blen:
359 if blen:
335 bstart += 1
360 bstart += 1
336
361
337 hunkrange = astart, alen, bstart, blen
362 hunkrange = astart, alen, bstart, blen
338 hunklines = (
363 hunklines = (
339 ["@@ -%d,%d +%d,%d @@%s\n" % (hunkrange + (func,))]
364 ["@@ -%d,%d +%d,%d @@%s\n" % (hunkrange + (func,))]
340 + delta
365 + delta
341 + [' ' + l1[x] for x in xrange(a2, aend)]
366 + [' ' + l1[x] for x in xrange(a2, aend)]
342 )
367 )
343 yield hunkrange, hunklines
368 yield hunkrange, hunklines
344
369
345 # bdiff.blocks gives us the matching sequences in the files. The loop
370 # bdiff.blocks gives us the matching sequences in the files. The loop
346 # below finds the spaces between those matching sequences and translates
371 # below finds the spaces between those matching sequences and translates
347 # them into diff output.
372 # them into diff output.
348 #
373 #
349 hunk = None
374 hunk = None
350 ignoredlines = 0
375 ignoredlines = 0
351 for s, stype in allblocks(t1, t2, opts, l1, l2):
376 for s, stype in allblocks(t1, t2, opts, l1, l2):
352 a1, a2, b1, b2 = s
377 a1, a2, b1, b2 = s
353 if stype != '!':
378 if stype != '!':
354 if stype == '~':
379 if stype == '~':
355 # The diff context lines are based on t1 content. When
380 # The diff context lines are based on t1 content. When
356 # blank lines are ignored, the new lines offsets must
381 # blank lines are ignored, the new lines offsets must
357 # be adjusted as if equivalent blocks ('~') had the
382 # be adjusted as if equivalent blocks ('~') had the
358 # same sizes on both sides.
383 # same sizes on both sides.
359 ignoredlines += (b2 - b1) - (a2 - a1)
384 ignoredlines += (b2 - b1) - (a2 - a1)
360 continue
385 continue
361 delta = []
386 delta = []
362 old = l1[a1:a2]
387 old = l1[a1:a2]
363 new = l2[b1:b2]
388 new = l2[b1:b2]
364
389
365 b1 -= ignoredlines
390 b1 -= ignoredlines
366 b2 -= ignoredlines
391 b2 -= ignoredlines
367 astart = contextstart(a1)
392 astart = contextstart(a1)
368 bstart = contextstart(b1)
393 bstart = contextstart(b1)
369 prev = None
394 prev = None
370 if hunk:
395 if hunk:
371 # join with the previous hunk if it falls inside the context
396 # join with the previous hunk if it falls inside the context
372 if astart < hunk[1] + opts.context + 1:
397 if astart < hunk[1] + opts.context + 1:
373 prev = hunk
398 prev = hunk
374 astart = hunk[1]
399 astart = hunk[1]
375 bstart = hunk[3]
400 bstart = hunk[3]
376 else:
401 else:
377 for x in yieldhunk(hunk):
402 for x in yieldhunk(hunk):
378 yield x
403 yield x
379 if prev:
404 if prev:
380 # we've joined the previous hunk, record the new ending points.
405 # we've joined the previous hunk, record the new ending points.
381 hunk[1] = a2
406 hunk[1] = a2
382 hunk[3] = b2
407 hunk[3] = b2
383 delta = hunk[4]
408 delta = hunk[4]
384 else:
409 else:
385 # create a new hunk
410 # create a new hunk
386 hunk = [astart, a2, bstart, b2, delta]
411 hunk = [astart, a2, bstart, b2, delta]
387
412
388 delta[len(delta):] = [' ' + x for x in l1[astart:a1]]
413 delta[len(delta):] = [' ' + x for x in l1[astart:a1]]
389 delta[len(delta):] = ['-' + x for x in old]
414 delta[len(delta):] = ['-' + x for x in old]
390 delta[len(delta):] = ['+' + x for x in new]
415 delta[len(delta):] = ['+' + x for x in new]
391
416
392 if hunk:
417 if hunk:
393 for x in yieldhunk(hunk):
418 for x in yieldhunk(hunk):
394 yield x
419 yield x
395
420
396 def b85diff(to, tn):
421 def b85diff(to, tn):
397 '''print base85-encoded binary diff'''
422 '''print base85-encoded binary diff'''
398 def fmtline(line):
423 def fmtline(line):
399 l = len(line)
424 l = len(line)
400 if l <= 26:
425 if l <= 26:
401 l = chr(ord('A') + l - 1)
426 l = chr(ord('A') + l - 1)
402 else:
427 else:
403 l = chr(l - 26 + ord('a') - 1)
428 l = chr(l - 26 + ord('a') - 1)
404 return '%c%s\n' % (l, base85.b85encode(line, True))
429 return '%c%s\n' % (l, base85.b85encode(line, True))
405
430
406 def chunk(text, csize=52):
431 def chunk(text, csize=52):
407 l = len(text)
432 l = len(text)
408 i = 0
433 i = 0
409 while i < l:
434 while i < l:
410 yield text[i:i + csize]
435 yield text[i:i + csize]
411 i += csize
436 i += csize
412
437
413 if to is None:
438 if to is None:
414 to = ''
439 to = ''
415 if tn is None:
440 if tn is None:
416 tn = ''
441 tn = ''
417
442
418 if to == tn:
443 if to == tn:
419 return ''
444 return ''
420
445
421 # TODO: deltas
446 # TODO: deltas
422 ret = []
447 ret = []
423 ret.append('GIT binary patch\n')
448 ret.append('GIT binary patch\n')
424 ret.append('literal %s\n' % len(tn))
449 ret.append('literal %s\n' % len(tn))
425 for l in chunk(zlib.compress(tn)):
450 for l in chunk(zlib.compress(tn)):
426 ret.append(fmtline(l))
451 ret.append(fmtline(l))
427 ret.append('\n')
452 ret.append('\n')
428
453
429 return ''.join(ret)
454 return ''.join(ret)
430
455
431 def patchtext(bin):
456 def patchtext(bin):
432 pos = 0
457 pos = 0
433 t = []
458 t = []
434 while pos < len(bin):
459 while pos < len(bin):
435 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
460 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
436 pos += 12
461 pos += 12
437 t.append(bin[pos:pos + l])
462 t.append(bin[pos:pos + l])
438 pos += l
463 pos += l
439 return "".join(t)
464 return "".join(t)
440
465
441 def patch(a, bin):
466 def patch(a, bin):
442 if len(a) == 0:
467 if len(a) == 0:
443 # skip over trivial delta header
468 # skip over trivial delta header
444 return util.buffer(bin, 12)
469 return util.buffer(bin, 12)
445 return mpatch.patches(a, [bin])
470 return mpatch.patches(a, [bin])
446
471
447 # similar to difflib.SequenceMatcher.get_matching_blocks
472 # similar to difflib.SequenceMatcher.get_matching_blocks
448 def get_matching_blocks(a, b):
473 def get_matching_blocks(a, b):
449 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
474 return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
450
475
451 def trivialdiffheader(length):
476 def trivialdiffheader(length):
452 return struct.pack(">lll", 0, 0, length) if length else ''
477 return struct.pack(">lll", 0, 0, length) if length else ''
453
478
454 def replacediffheader(oldlen, newlen):
479 def replacediffheader(oldlen, newlen):
455 return struct.pack(">lll", 0, oldlen, newlen)
480 return struct.pack(">lll", 0, oldlen, newlen)
456
481
457 patches = mpatch.patches
482 patches = mpatch.patches
458 patchedsize = mpatch.patchedsize
483 patchedsize = mpatch.patchedsize
459 textdiff = bdiff.bdiff
484 textdiff = bdiff.bdiff
@@ -1,55 +1,56 b''
1 # this is hack to make sure no escape characters are inserted into the output
1 # this is hack to make sure no escape characters are inserted into the output
2
2
3 from __future__ import absolute_import
3 from __future__ import absolute_import
4
4
5 import doctest
5 import doctest
6 import os
6 import os
7 import sys
7 import sys
8
8
9 ispy3 = (sys.version_info[0] >= 3)
9 ispy3 = (sys.version_info[0] >= 3)
10
10
11 if 'TERM' in os.environ:
11 if 'TERM' in os.environ:
12 del os.environ['TERM']
12 del os.environ['TERM']
13
13
14 # TODO: migrate doctests to py3 and enable them on both versions
14 # TODO: migrate doctests to py3 and enable them on both versions
15 def testmod(name, optionflags=0, testtarget=None, py2=True, py3=False):
15 def testmod(name, optionflags=0, testtarget=None, py2=True, py3=False):
16 if not (not ispy3 and py2 or ispy3 and py3):
16 if not (not ispy3 and py2 or ispy3 and py3):
17 return
17 return
18 __import__(name)
18 __import__(name)
19 mod = sys.modules[name]
19 mod = sys.modules[name]
20 if testtarget is not None:
20 if testtarget is not None:
21 mod = getattr(mod, testtarget)
21 mod = getattr(mod, testtarget)
22 doctest.testmod(mod, optionflags=optionflags)
22 doctest.testmod(mod, optionflags=optionflags)
23
23
24 testmod('mercurial.changegroup')
24 testmod('mercurial.changegroup')
25 testmod('mercurial.changelog')
25 testmod('mercurial.changelog')
26 testmod('mercurial.color')
26 testmod('mercurial.color')
27 testmod('mercurial.config')
27 testmod('mercurial.config')
28 testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
28 testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
29 testmod('mercurial.dispatch')
29 testmod('mercurial.dispatch')
30 testmod('mercurial.encoding')
30 testmod('mercurial.encoding')
31 testmod('mercurial.formatter')
31 testmod('mercurial.formatter')
32 testmod('mercurial.hg')
32 testmod('mercurial.hg')
33 testmod('mercurial.hgweb.hgwebdir_mod')
33 testmod('mercurial.hgweb.hgwebdir_mod')
34 testmod('mercurial.match')
34 testmod('mercurial.match')
35 testmod('mercurial.mdiff')
35 testmod('mercurial.minirst')
36 testmod('mercurial.minirst')
36 testmod('mercurial.patch')
37 testmod('mercurial.patch')
37 testmod('mercurial.pathutil')
38 testmod('mercurial.pathutil')
38 testmod('mercurial.parser')
39 testmod('mercurial.parser')
39 testmod('mercurial.pycompat', py3=True)
40 testmod('mercurial.pycompat', py3=True)
40 testmod('mercurial.revsetlang')
41 testmod('mercurial.revsetlang')
41 testmod('mercurial.smartset')
42 testmod('mercurial.smartset')
42 testmod('mercurial.store')
43 testmod('mercurial.store')
43 testmod('mercurial.subrepo')
44 testmod('mercurial.subrepo')
44 testmod('mercurial.templatefilters')
45 testmod('mercurial.templatefilters')
45 testmod('mercurial.templater')
46 testmod('mercurial.templater')
46 testmod('mercurial.ui')
47 testmod('mercurial.ui')
47 testmod('mercurial.url')
48 testmod('mercurial.url')
48 testmod('mercurial.util')
49 testmod('mercurial.util')
49 testmod('mercurial.util', testtarget='platform')
50 testmod('mercurial.util', testtarget='platform')
50 testmod('hgext.convert.convcmd')
51 testmod('hgext.convert.convcmd')
51 testmod('hgext.convert.cvsps')
52 testmod('hgext.convert.cvsps')
52 testmod('hgext.convert.filemap')
53 testmod('hgext.convert.filemap')
53 testmod('hgext.convert.p4')
54 testmod('hgext.convert.p4')
54 testmod('hgext.convert.subversion')
55 testmod('hgext.convert.subversion')
55 testmod('hgext.mq')
56 testmod('hgext.mq')
General Comments 0
You need to be logged in to leave comments. Login now