##// END OF EJS Templates
keyword: use absolute_import
Christian Ebert -
r28321:a7b453b4 default
parent child Browse files
Show More
@@ -1,734 +1,754 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007-2015 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007-2015 Christian Ebert <blacktrash@gmx.net>
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 # $Id$
8 # $Id$
9 #
9 #
10 # Keyword expansion hack against the grain of a Distributed SCM
10 # Keyword expansion hack against the grain of a Distributed SCM
11 #
11 #
12 # There are many good reasons why this is not needed in a distributed
12 # There are many good reasons why this is not needed in a distributed
13 # SCM, still it may be useful in very small projects based on single
13 # SCM, still it may be useful in very small projects based on single
14 # files (like LaTeX packages), that are mostly addressed to an
14 # files (like LaTeX packages), that are mostly addressed to an
15 # audience not running a version control system.
15 # audience not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <https://mercurial-scm.org/wiki/KeywordPlan>.
18 # <https://mercurial-scm.org/wiki/KeywordPlan>.
19 #
19 #
20 # Keyword expansion is based on Mercurial's changeset template mappings.
20 # Keyword expansion is based on Mercurial's changeset template mappings.
21 #
21 #
22 # Binary files are not touched.
22 # Binary files are not touched.
23 #
23 #
24 # Files to act upon/ignore are specified in the [keyword] section.
24 # Files to act upon/ignore are specified in the [keyword] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
25 # Customized keyword template mappings in the [keywordmaps] section.
26 #
26 #
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
27 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
28
28
29 '''expand keywords in tracked files
29 '''expand keywords in tracked files
30
30
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
31 This extension expands RCS/CVS-like or self-customized $Keywords$ in
32 tracked text files selected by your configuration.
32 tracked text files selected by your configuration.
33
33
34 Keywords are only expanded in local repositories and not stored in the
34 Keywords are only expanded in local repositories and not stored in the
35 change history. The mechanism can be regarded as a convenience for the
35 change history. The mechanism can be regarded as a convenience for the
36 current user or for archive distribution.
36 current user or for archive distribution.
37
37
38 Keywords expand to the changeset data pertaining to the latest change
38 Keywords expand to the changeset data pertaining to the latest change
39 relative to the working directory parent of each file.
39 relative to the working directory parent of each file.
40
40
41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
41 Configuration is done in the [keyword], [keywordset] and [keywordmaps]
42 sections of hgrc files.
42 sections of hgrc files.
43
43
44 Example::
44 Example::
45
45
46 [keyword]
46 [keyword]
47 # expand keywords in every python file except those matching "x*"
47 # expand keywords in every python file except those matching "x*"
48 **.py =
48 **.py =
49 x* = ignore
49 x* = ignore
50
50
51 [keywordset]
51 [keywordset]
52 # prefer svn- over cvs-like default keywordmaps
52 # prefer svn- over cvs-like default keywordmaps
53 svn = True
53 svn = True
54
54
55 .. note::
55 .. note::
56
56
57 The more specific you are in your filename patterns the less you
57 The more specific you are in your filename patterns the less you
58 lose speed in huge repositories.
58 lose speed in huge repositories.
59
59
60 For [keywordmaps] template mapping and expansion demonstration and
60 For [keywordmaps] template mapping and expansion demonstration and
61 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
61 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
62 available templates and filters.
62 available templates and filters.
63
63
64 Three additional date template filters are provided:
64 Three additional date template filters are provided:
65
65
66 :``utcdate``: "2006/09/18 15:13:13"
66 :``utcdate``: "2006/09/18 15:13:13"
67 :``svnutcdate``: "2006-09-18 15:13:13Z"
67 :``svnutcdate``: "2006-09-18 15:13:13Z"
68 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
68 :``svnisodate``: "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
69
69
70 The default template mappings (view with :hg:`kwdemo -d`) can be
70 The default template mappings (view with :hg:`kwdemo -d`) can be
71 replaced with customized keywords and templates. Again, run
71 replaced with customized keywords and templates. Again, run
72 :hg:`kwdemo` to control the results of your configuration changes.
72 :hg:`kwdemo` to control the results of your configuration changes.
73
73
74 Before changing/disabling active keywords, you must run :hg:`kwshrink`
74 Before changing/disabling active keywords, you must run :hg:`kwshrink`
75 to avoid storing expanded keywords in the change history.
75 to avoid storing expanded keywords in the change history.
76
76
77 To force expansion after enabling it, or a configuration change, run
77 To force expansion after enabling it, or a configuration change, run
78 :hg:`kwexpand`.
78 :hg:`kwexpand`.
79
79
80 Expansions spanning more than one line and incremental expansions,
80 Expansions spanning more than one line and incremental expansions,
81 like CVS' $Log$, are not supported. A keyword template map "Log =
81 like CVS' $Log$, are not supported. A keyword template map "Log =
82 {desc}" expands to the first line of the changeset description.
82 {desc}" expands to the first line of the changeset description.
83 '''
83 '''
84
84
85 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
85
86 from mercurial import localrepo, match, patch, templatefilters, util, error
86 from __future__ import absolute_import
87 from mercurial import scmutil, pathutil
87
88 import os
89 import re
90 import tempfile
91
88 from mercurial.hgweb import webcommands
92 from mercurial.hgweb import webcommands
89 from mercurial.i18n import _
93 from mercurial.i18n import _
90 import os, re, tempfile
94
95 from mercurial import (
96 cmdutil,
97 commands,
98 context,
99 dispatch,
100 error,
101 extensions,
102 filelog,
103 localrepo,
104 match,
105 patch,
106 pathutil,
107 scmutil,
108 templatefilters,
109 util,
110 )
91
111
92 cmdtable = {}
112 cmdtable = {}
93 command = cmdutil.command(cmdtable)
113 command = cmdutil.command(cmdtable)
94 # Note for extension authors: ONLY specify testedwith = 'internal' for
114 # Note for extension authors: ONLY specify testedwith = 'internal' for
95 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
115 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
96 # be specifying the version(s) of Mercurial they are tested with, or
116 # be specifying the version(s) of Mercurial they are tested with, or
97 # leave the attribute unspecified.
117 # leave the attribute unspecified.
98 testedwith = 'internal'
118 testedwith = 'internal'
99
119
100 # hg commands that do not act on keywords
120 # hg commands that do not act on keywords
101 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
121 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
102 ' outgoing push tip verify convert email glog')
122 ' outgoing push tip verify convert email glog')
103
123
104 # hg commands that trigger expansion only when writing to working dir,
124 # hg commands that trigger expansion only when writing to working dir,
105 # not when reading filelog, and unexpand when reading from working dir
125 # not when reading filelog, and unexpand when reading from working dir
106 restricted = ('merge kwexpand kwshrink record qrecord resolve transplant'
126 restricted = ('merge kwexpand kwshrink record qrecord resolve transplant'
107 ' unshelve rebase graft backout histedit fetch')
127 ' unshelve rebase graft backout histedit fetch')
108
128
109 # names of extensions using dorecord
129 # names of extensions using dorecord
110 recordextensions = 'record'
130 recordextensions = 'record'
111
131
112 colortable = {
132 colortable = {
113 'kwfiles.enabled': 'green bold',
133 'kwfiles.enabled': 'green bold',
114 'kwfiles.deleted': 'cyan bold underline',
134 'kwfiles.deleted': 'cyan bold underline',
115 'kwfiles.enabledunknown': 'green',
135 'kwfiles.enabledunknown': 'green',
116 'kwfiles.ignored': 'bold',
136 'kwfiles.ignored': 'bold',
117 'kwfiles.ignoredunknown': 'none'
137 'kwfiles.ignoredunknown': 'none'
118 }
138 }
119
139
120 # date like in cvs' $Date
140 # date like in cvs' $Date
121 def utcdate(text):
141 def utcdate(text):
122 ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
142 ''':utcdate: Date. Returns a UTC-date in this format: "2009/08/18 11:00:13".
123 '''
143 '''
124 return util.datestr((util.parsedate(text)[0], 0), '%Y/%m/%d %H:%M:%S')
144 return util.datestr((util.parsedate(text)[0], 0), '%Y/%m/%d %H:%M:%S')
125 # date like in svn's $Date
145 # date like in svn's $Date
126 def svnisodate(text):
146 def svnisodate(text):
127 ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13
147 ''':svnisodate: Date. Returns a date in this format: "2009-08-18 13:00:13
128 +0200 (Tue, 18 Aug 2009)".
148 +0200 (Tue, 18 Aug 2009)".
129 '''
149 '''
130 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
150 return util.datestr(text, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
131 # date like in svn's $Id
151 # date like in svn's $Id
132 def svnutcdate(text):
152 def svnutcdate(text):
133 ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18
153 ''':svnutcdate: Date. Returns a UTC-date in this format: "2009-08-18
134 11:00:13Z".
154 11:00:13Z".
135 '''
155 '''
136 return util.datestr((util.parsedate(text)[0], 0), '%Y-%m-%d %H:%M:%SZ')
156 return util.datestr((util.parsedate(text)[0], 0), '%Y-%m-%d %H:%M:%SZ')
137
157
138 templatefilters.filters.update({'utcdate': utcdate,
158 templatefilters.filters.update({'utcdate': utcdate,
139 'svnisodate': svnisodate,
159 'svnisodate': svnisodate,
140 'svnutcdate': svnutcdate})
160 'svnutcdate': svnutcdate})
141
161
142 # make keyword tools accessible
162 # make keyword tools accessible
143 kwtools = {'templater': None, 'hgcmd': ''}
163 kwtools = {'templater': None, 'hgcmd': ''}
144
164
145 def _defaultkwmaps(ui):
165 def _defaultkwmaps(ui):
146 '''Returns default keywordmaps according to keywordset configuration.'''
166 '''Returns default keywordmaps according to keywordset configuration.'''
147 templates = {
167 templates = {
148 'Revision': '{node|short}',
168 'Revision': '{node|short}',
149 'Author': '{author|user}',
169 'Author': '{author|user}',
150 }
170 }
151 kwsets = ({
171 kwsets = ({
152 'Date': '{date|utcdate}',
172 'Date': '{date|utcdate}',
153 'RCSfile': '{file|basename},v',
173 'RCSfile': '{file|basename},v',
154 'RCSFile': '{file|basename},v', # kept for backwards compatibility
174 'RCSFile': '{file|basename},v', # kept for backwards compatibility
155 # with hg-keyword
175 # with hg-keyword
156 'Source': '{root}/{file},v',
176 'Source': '{root}/{file},v',
157 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
177 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
158 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
178 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
159 }, {
179 }, {
160 'Date': '{date|svnisodate}',
180 'Date': '{date|svnisodate}',
161 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
181 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
162 'LastChangedRevision': '{node|short}',
182 'LastChangedRevision': '{node|short}',
163 'LastChangedBy': '{author|user}',
183 'LastChangedBy': '{author|user}',
164 'LastChangedDate': '{date|svnisodate}',
184 'LastChangedDate': '{date|svnisodate}',
165 })
185 })
166 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
186 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
167 return templates
187 return templates
168
188
169 def _shrinktext(text, subfunc):
189 def _shrinktext(text, subfunc):
170 '''Helper for keyword expansion removal in text.
190 '''Helper for keyword expansion removal in text.
171 Depending on subfunc also returns number of substitutions.'''
191 Depending on subfunc also returns number of substitutions.'''
172 return subfunc(r'$\1$', text)
192 return subfunc(r'$\1$', text)
173
193
174 def _preselect(wstatus, changed):
194 def _preselect(wstatus, changed):
175 '''Retrieves modified and added files from a working directory state
195 '''Retrieves modified and added files from a working directory state
176 and returns the subset of each contained in given changed files
196 and returns the subset of each contained in given changed files
177 retrieved from a change context.'''
197 retrieved from a change context.'''
178 modified = [f for f in wstatus.modified if f in changed]
198 modified = [f for f in wstatus.modified if f in changed]
179 added = [f for f in wstatus.added if f in changed]
199 added = [f for f in wstatus.added if f in changed]
180 return modified, added
200 return modified, added
181
201
182
202
183 class kwtemplater(object):
203 class kwtemplater(object):
184 '''
204 '''
185 Sets up keyword templates, corresponding keyword regex, and
205 Sets up keyword templates, corresponding keyword regex, and
186 provides keyword substitution functions.
206 provides keyword substitution functions.
187 '''
207 '''
188
208
189 def __init__(self, ui, repo, inc, exc):
209 def __init__(self, ui, repo, inc, exc):
190 self.ui = ui
210 self.ui = ui
191 self.repo = repo
211 self.repo = repo
192 self.match = match.match(repo.root, '', [], inc, exc)
212 self.match = match.match(repo.root, '', [], inc, exc)
193 self.restrict = kwtools['hgcmd'] in restricted.split()
213 self.restrict = kwtools['hgcmd'] in restricted.split()
194 self.postcommit = False
214 self.postcommit = False
195
215
196 kwmaps = self.ui.configitems('keywordmaps')
216 kwmaps = self.ui.configitems('keywordmaps')
197 if kwmaps: # override default templates
217 if kwmaps: # override default templates
198 self.templates = dict(kwmaps)
218 self.templates = dict(kwmaps)
199 else:
219 else:
200 self.templates = _defaultkwmaps(self.ui)
220 self.templates = _defaultkwmaps(self.ui)
201
221
202 @util.propertycache
222 @util.propertycache
203 def escape(self):
223 def escape(self):
204 '''Returns bar-separated and escaped keywords.'''
224 '''Returns bar-separated and escaped keywords.'''
205 return '|'.join(map(re.escape, self.templates.keys()))
225 return '|'.join(map(re.escape, self.templates.keys()))
206
226
207 @util.propertycache
227 @util.propertycache
208 def rekw(self):
228 def rekw(self):
209 '''Returns regex for unexpanded keywords.'''
229 '''Returns regex for unexpanded keywords.'''
210 return re.compile(r'\$(%s)\$' % self.escape)
230 return re.compile(r'\$(%s)\$' % self.escape)
211
231
212 @util.propertycache
232 @util.propertycache
213 def rekwexp(self):
233 def rekwexp(self):
214 '''Returns regex for expanded keywords.'''
234 '''Returns regex for expanded keywords.'''
215 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
235 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
216
236
217 def substitute(self, data, path, ctx, subfunc):
237 def substitute(self, data, path, ctx, subfunc):
218 '''Replaces keywords in data with expanded template.'''
238 '''Replaces keywords in data with expanded template.'''
219 def kwsub(mobj):
239 def kwsub(mobj):
220 kw = mobj.group(1)
240 kw = mobj.group(1)
221 ct = cmdutil.changeset_templater(self.ui, self.repo, False, None,
241 ct = cmdutil.changeset_templater(self.ui, self.repo, False, None,
222 self.templates[kw], '', False)
242 self.templates[kw], '', False)
223 self.ui.pushbuffer()
243 self.ui.pushbuffer()
224 ct.show(ctx, root=self.repo.root, file=path)
244 ct.show(ctx, root=self.repo.root, file=path)
225 ekw = templatefilters.firstline(self.ui.popbuffer())
245 ekw = templatefilters.firstline(self.ui.popbuffer())
226 return '$%s: %s $' % (kw, ekw)
246 return '$%s: %s $' % (kw, ekw)
227 return subfunc(kwsub, data)
247 return subfunc(kwsub, data)
228
248
229 def linkctx(self, path, fileid):
249 def linkctx(self, path, fileid):
230 '''Similar to filelog.linkrev, but returns a changectx.'''
250 '''Similar to filelog.linkrev, but returns a changectx.'''
231 return self.repo.filectx(path, fileid=fileid).changectx()
251 return self.repo.filectx(path, fileid=fileid).changectx()
232
252
233 def expand(self, path, node, data):
253 def expand(self, path, node, data):
234 '''Returns data with keywords expanded.'''
254 '''Returns data with keywords expanded.'''
235 if not self.restrict and self.match(path) and not util.binary(data):
255 if not self.restrict and self.match(path) and not util.binary(data):
236 ctx = self.linkctx(path, node)
256 ctx = self.linkctx(path, node)
237 return self.substitute(data, path, ctx, self.rekw.sub)
257 return self.substitute(data, path, ctx, self.rekw.sub)
238 return data
258 return data
239
259
240 def iskwfile(self, cand, ctx):
260 def iskwfile(self, cand, ctx):
241 '''Returns subset of candidates which are configured for keyword
261 '''Returns subset of candidates which are configured for keyword
242 expansion but are not symbolic links.'''
262 expansion but are not symbolic links.'''
243 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)]
263 return [f for f in cand if self.match(f) and 'l' not in ctx.flags(f)]
244
264
245 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
265 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
246 '''Overwrites selected files expanding/shrinking keywords.'''
266 '''Overwrites selected files expanding/shrinking keywords.'''
247 if self.restrict or lookup or self.postcommit: # exclude kw_copy
267 if self.restrict or lookup or self.postcommit: # exclude kw_copy
248 candidates = self.iskwfile(candidates, ctx)
268 candidates = self.iskwfile(candidates, ctx)
249 if not candidates:
269 if not candidates:
250 return
270 return
251 kwcmd = self.restrict and lookup # kwexpand/kwshrink
271 kwcmd = self.restrict and lookup # kwexpand/kwshrink
252 if self.restrict or expand and lookup:
272 if self.restrict or expand and lookup:
253 mf = ctx.manifest()
273 mf = ctx.manifest()
254 if self.restrict or rekw:
274 if self.restrict or rekw:
255 re_kw = self.rekw
275 re_kw = self.rekw
256 else:
276 else:
257 re_kw = self.rekwexp
277 re_kw = self.rekwexp
258 if expand:
278 if expand:
259 msg = _('overwriting %s expanding keywords\n')
279 msg = _('overwriting %s expanding keywords\n')
260 else:
280 else:
261 msg = _('overwriting %s shrinking keywords\n')
281 msg = _('overwriting %s shrinking keywords\n')
262 for f in candidates:
282 for f in candidates:
263 if self.restrict:
283 if self.restrict:
264 data = self.repo.file(f).read(mf[f])
284 data = self.repo.file(f).read(mf[f])
265 else:
285 else:
266 data = self.repo.wread(f)
286 data = self.repo.wread(f)
267 if util.binary(data):
287 if util.binary(data):
268 continue
288 continue
269 if expand:
289 if expand:
270 parents = ctx.parents()
290 parents = ctx.parents()
271 if lookup:
291 if lookup:
272 ctx = self.linkctx(f, mf[f])
292 ctx = self.linkctx(f, mf[f])
273 elif self.restrict and len(parents) > 1:
293 elif self.restrict and len(parents) > 1:
274 # merge commit
294 # merge commit
275 # in case of conflict f is in modified state during
295 # in case of conflict f is in modified state during
276 # merge, even if f does not differ from f in parent
296 # merge, even if f does not differ from f in parent
277 for p in parents:
297 for p in parents:
278 if f in p and not p[f].cmp(ctx[f]):
298 if f in p and not p[f].cmp(ctx[f]):
279 ctx = p[f].changectx()
299 ctx = p[f].changectx()
280 break
300 break
281 data, found = self.substitute(data, f, ctx, re_kw.subn)
301 data, found = self.substitute(data, f, ctx, re_kw.subn)
282 elif self.restrict:
302 elif self.restrict:
283 found = re_kw.search(data)
303 found = re_kw.search(data)
284 else:
304 else:
285 data, found = _shrinktext(data, re_kw.subn)
305 data, found = _shrinktext(data, re_kw.subn)
286 if found:
306 if found:
287 self.ui.note(msg % f)
307 self.ui.note(msg % f)
288 fp = self.repo.wvfs(f, "wb", atomictemp=True)
308 fp = self.repo.wvfs(f, "wb", atomictemp=True)
289 fp.write(data)
309 fp.write(data)
290 fp.close()
310 fp.close()
291 if kwcmd:
311 if kwcmd:
292 self.repo.dirstate.normal(f)
312 self.repo.dirstate.normal(f)
293 elif self.postcommit:
313 elif self.postcommit:
294 self.repo.dirstate.normallookup(f)
314 self.repo.dirstate.normallookup(f)
295
315
296 def shrink(self, fname, text):
316 def shrink(self, fname, text):
297 '''Returns text with all keyword substitutions removed.'''
317 '''Returns text with all keyword substitutions removed.'''
298 if self.match(fname) and not util.binary(text):
318 if self.match(fname) and not util.binary(text):
299 return _shrinktext(text, self.rekwexp.sub)
319 return _shrinktext(text, self.rekwexp.sub)
300 return text
320 return text
301
321
302 def shrinklines(self, fname, lines):
322 def shrinklines(self, fname, lines):
303 '''Returns lines with keyword substitutions removed.'''
323 '''Returns lines with keyword substitutions removed.'''
304 if self.match(fname):
324 if self.match(fname):
305 text = ''.join(lines)
325 text = ''.join(lines)
306 if not util.binary(text):
326 if not util.binary(text):
307 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
327 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
308 return lines
328 return lines
309
329
310 def wread(self, fname, data):
330 def wread(self, fname, data):
311 '''If in restricted mode returns data read from wdir with
331 '''If in restricted mode returns data read from wdir with
312 keyword substitutions removed.'''
332 keyword substitutions removed.'''
313 if self.restrict:
333 if self.restrict:
314 return self.shrink(fname, data)
334 return self.shrink(fname, data)
315 return data
335 return data
316
336
317 class kwfilelog(filelog.filelog):
337 class kwfilelog(filelog.filelog):
318 '''
338 '''
319 Subclass of filelog to hook into its read, add, cmp methods.
339 Subclass of filelog to hook into its read, add, cmp methods.
320 Keywords are "stored" unexpanded, and processed on reading.
340 Keywords are "stored" unexpanded, and processed on reading.
321 '''
341 '''
322 def __init__(self, opener, kwt, path):
342 def __init__(self, opener, kwt, path):
323 super(kwfilelog, self).__init__(opener, path)
343 super(kwfilelog, self).__init__(opener, path)
324 self.kwt = kwt
344 self.kwt = kwt
325 self.path = path
345 self.path = path
326
346
327 def read(self, node):
347 def read(self, node):
328 '''Expands keywords when reading filelog.'''
348 '''Expands keywords when reading filelog.'''
329 data = super(kwfilelog, self).read(node)
349 data = super(kwfilelog, self).read(node)
330 if self.renamed(node):
350 if self.renamed(node):
331 return data
351 return data
332 return self.kwt.expand(self.path, node, data)
352 return self.kwt.expand(self.path, node, data)
333
353
334 def add(self, text, meta, tr, link, p1=None, p2=None):
354 def add(self, text, meta, tr, link, p1=None, p2=None):
335 '''Removes keyword substitutions when adding to filelog.'''
355 '''Removes keyword substitutions when adding to filelog.'''
336 text = self.kwt.shrink(self.path, text)
356 text = self.kwt.shrink(self.path, text)
337 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
357 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
338
358
339 def cmp(self, node, text):
359 def cmp(self, node, text):
340 '''Removes keyword substitutions for comparison.'''
360 '''Removes keyword substitutions for comparison.'''
341 text = self.kwt.shrink(self.path, text)
361 text = self.kwt.shrink(self.path, text)
342 return super(kwfilelog, self).cmp(node, text)
362 return super(kwfilelog, self).cmp(node, text)
343
363
344 def _status(ui, repo, wctx, kwt, *pats, **opts):
364 def _status(ui, repo, wctx, kwt, *pats, **opts):
345 '''Bails out if [keyword] configuration is not active.
365 '''Bails out if [keyword] configuration is not active.
346 Returns status of working directory.'''
366 Returns status of working directory.'''
347 if kwt:
367 if kwt:
348 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
368 return repo.status(match=scmutil.match(wctx, pats, opts), clean=True,
349 unknown=opts.get('unknown') or opts.get('all'))
369 unknown=opts.get('unknown') or opts.get('all'))
350 if ui.configitems('keyword'):
370 if ui.configitems('keyword'):
351 raise error.Abort(_('[keyword] patterns cannot match'))
371 raise error.Abort(_('[keyword] patterns cannot match'))
352 raise error.Abort(_('no [keyword] patterns configured'))
372 raise error.Abort(_('no [keyword] patterns configured'))
353
373
354 def _kwfwrite(ui, repo, expand, *pats, **opts):
374 def _kwfwrite(ui, repo, expand, *pats, **opts):
355 '''Selects files and passes them to kwtemplater.overwrite.'''
375 '''Selects files and passes them to kwtemplater.overwrite.'''
356 wctx = repo[None]
376 wctx = repo[None]
357 if len(wctx.parents()) > 1:
377 if len(wctx.parents()) > 1:
358 raise error.Abort(_('outstanding uncommitted merge'))
378 raise error.Abort(_('outstanding uncommitted merge'))
359 kwt = kwtools['templater']
379 kwt = kwtools['templater']
360 with repo.wlock():
380 with repo.wlock():
361 status = _status(ui, repo, wctx, kwt, *pats, **opts)
381 status = _status(ui, repo, wctx, kwt, *pats, **opts)
362 if status.modified or status.added or status.removed or status.deleted:
382 if status.modified or status.added or status.removed or status.deleted:
363 raise error.Abort(_('outstanding uncommitted changes'))
383 raise error.Abort(_('outstanding uncommitted changes'))
364 kwt.overwrite(wctx, status.clean, True, expand)
384 kwt.overwrite(wctx, status.clean, True, expand)
365
385
366 @command('kwdemo',
386 @command('kwdemo',
367 [('d', 'default', None, _('show default keyword template maps')),
387 [('d', 'default', None, _('show default keyword template maps')),
368 ('f', 'rcfile', '',
388 ('f', 'rcfile', '',
369 _('read maps from rcfile'), _('FILE'))],
389 _('read maps from rcfile'), _('FILE'))],
370 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'),
390 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...'),
371 optionalrepo=True)
391 optionalrepo=True)
372 def demo(ui, repo, *args, **opts):
392 def demo(ui, repo, *args, **opts):
373 '''print [keywordmaps] configuration and an expansion example
393 '''print [keywordmaps] configuration and an expansion example
374
394
375 Show current, custom, or default keyword template maps and their
395 Show current, custom, or default keyword template maps and their
376 expansions.
396 expansions.
377
397
378 Extend the current configuration by specifying maps as arguments
398 Extend the current configuration by specifying maps as arguments
379 and using -f/--rcfile to source an external hgrc file.
399 and using -f/--rcfile to source an external hgrc file.
380
400
381 Use -d/--default to disable current configuration.
401 Use -d/--default to disable current configuration.
382
402
383 See :hg:`help templates` for information on templates and filters.
403 See :hg:`help templates` for information on templates and filters.
384 '''
404 '''
385 def demoitems(section, items):
405 def demoitems(section, items):
386 ui.write('[%s]\n' % section)
406 ui.write('[%s]\n' % section)
387 for k, v in sorted(items):
407 for k, v in sorted(items):
388 ui.write('%s = %s\n' % (k, v))
408 ui.write('%s = %s\n' % (k, v))
389
409
390 fn = 'demo.txt'
410 fn = 'demo.txt'
391 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
411 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
392 ui.note(_('creating temporary repository at %s\n') % tmpdir)
412 ui.note(_('creating temporary repository at %s\n') % tmpdir)
393 repo = localrepo.localrepository(repo.baseui, tmpdir, True)
413 repo = localrepo.localrepository(repo.baseui, tmpdir, True)
394 ui.setconfig('keyword', fn, '', 'keyword')
414 ui.setconfig('keyword', fn, '', 'keyword')
395 svn = ui.configbool('keywordset', 'svn')
415 svn = ui.configbool('keywordset', 'svn')
396 # explicitly set keywordset for demo output
416 # explicitly set keywordset for demo output
397 ui.setconfig('keywordset', 'svn', svn, 'keyword')
417 ui.setconfig('keywordset', 'svn', svn, 'keyword')
398
418
399 uikwmaps = ui.configitems('keywordmaps')
419 uikwmaps = ui.configitems('keywordmaps')
400 if args or opts.get('rcfile'):
420 if args or opts.get('rcfile'):
401 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
421 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
402 if uikwmaps:
422 if uikwmaps:
403 ui.status(_('\textending current template maps\n'))
423 ui.status(_('\textending current template maps\n'))
404 if opts.get('default') or not uikwmaps:
424 if opts.get('default') or not uikwmaps:
405 if svn:
425 if svn:
406 ui.status(_('\toverriding default svn keywordset\n'))
426 ui.status(_('\toverriding default svn keywordset\n'))
407 else:
427 else:
408 ui.status(_('\toverriding default cvs keywordset\n'))
428 ui.status(_('\toverriding default cvs keywordset\n'))
409 if opts.get('rcfile'):
429 if opts.get('rcfile'):
410 ui.readconfig(opts.get('rcfile'))
430 ui.readconfig(opts.get('rcfile'))
411 if args:
431 if args:
412 # simulate hgrc parsing
432 # simulate hgrc parsing
413 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
433 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
414 fp = repo.vfs('hgrc', 'w')
434 fp = repo.vfs('hgrc', 'w')
415 fp.writelines(rcmaps)
435 fp.writelines(rcmaps)
416 fp.close()
436 fp.close()
417 ui.readconfig(repo.join('hgrc'))
437 ui.readconfig(repo.join('hgrc'))
418 kwmaps = dict(ui.configitems('keywordmaps'))
438 kwmaps = dict(ui.configitems('keywordmaps'))
419 elif opts.get('default'):
439 elif opts.get('default'):
420 if svn:
440 if svn:
421 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
441 ui.status(_('\n\tconfiguration using default svn keywordset\n'))
422 else:
442 else:
423 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
443 ui.status(_('\n\tconfiguration using default cvs keywordset\n'))
424 kwmaps = _defaultkwmaps(ui)
444 kwmaps = _defaultkwmaps(ui)
425 if uikwmaps:
445 if uikwmaps:
426 ui.status(_('\tdisabling current template maps\n'))
446 ui.status(_('\tdisabling current template maps\n'))
427 for k, v in kwmaps.iteritems():
447 for k, v in kwmaps.iteritems():
428 ui.setconfig('keywordmaps', k, v, 'keyword')
448 ui.setconfig('keywordmaps', k, v, 'keyword')
429 else:
449 else:
430 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
450 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
431 if uikwmaps:
451 if uikwmaps:
432 kwmaps = dict(uikwmaps)
452 kwmaps = dict(uikwmaps)
433 else:
453 else:
434 kwmaps = _defaultkwmaps(ui)
454 kwmaps = _defaultkwmaps(ui)
435
455
436 uisetup(ui)
456 uisetup(ui)
437 reposetup(ui, repo)
457 reposetup(ui, repo)
438 ui.write('[extensions]\nkeyword =\n')
458 ui.write('[extensions]\nkeyword =\n')
439 demoitems('keyword', ui.configitems('keyword'))
459 demoitems('keyword', ui.configitems('keyword'))
440 demoitems('keywordset', ui.configitems('keywordset'))
460 demoitems('keywordset', ui.configitems('keywordset'))
441 demoitems('keywordmaps', kwmaps.iteritems())
461 demoitems('keywordmaps', kwmaps.iteritems())
442 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
462 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
443 repo.wvfs.write(fn, keywords)
463 repo.wvfs.write(fn, keywords)
444 repo[None].add([fn])
464 repo[None].add([fn])
445 ui.note(_('\nkeywords written to %s:\n') % fn)
465 ui.note(_('\nkeywords written to %s:\n') % fn)
446 ui.note(keywords)
466 ui.note(keywords)
447 with repo.wlock():
467 with repo.wlock():
448 repo.dirstate.setbranch('demobranch')
468 repo.dirstate.setbranch('demobranch')
449 for name, cmd in ui.configitems('hooks'):
469 for name, cmd in ui.configitems('hooks'):
450 if name.split('.', 1)[0].find('commit') > -1:
470 if name.split('.', 1)[0].find('commit') > -1:
451 repo.ui.setconfig('hooks', name, '', 'keyword')
471 repo.ui.setconfig('hooks', name, '', 'keyword')
452 msg = _('hg keyword configuration and expansion example')
472 msg = _('hg keyword configuration and expansion example')
453 ui.note(("hg ci -m '%s'\n" % msg))
473 ui.note(("hg ci -m '%s'\n" % msg))
454 repo.commit(text=msg)
474 repo.commit(text=msg)
455 ui.status(_('\n\tkeywords expanded\n'))
475 ui.status(_('\n\tkeywords expanded\n'))
456 ui.write(repo.wread(fn))
476 ui.write(repo.wread(fn))
457 repo.wvfs.rmtree(repo.root)
477 repo.wvfs.rmtree(repo.root)
458
478
459 @command('kwexpand',
479 @command('kwexpand',
460 commands.walkopts,
480 commands.walkopts,
461 _('hg kwexpand [OPTION]... [FILE]...'),
481 _('hg kwexpand [OPTION]... [FILE]...'),
462 inferrepo=True)
482 inferrepo=True)
463 def expand(ui, repo, *pats, **opts):
483 def expand(ui, repo, *pats, **opts):
464 '''expand keywords in the working directory
484 '''expand keywords in the working directory
465
485
466 Run after (re)enabling keyword expansion.
486 Run after (re)enabling keyword expansion.
467
487
468 kwexpand refuses to run if given files contain local changes.
488 kwexpand refuses to run if given files contain local changes.
469 '''
489 '''
470 # 3rd argument sets expansion to True
490 # 3rd argument sets expansion to True
471 _kwfwrite(ui, repo, True, *pats, **opts)
491 _kwfwrite(ui, repo, True, *pats, **opts)
472
492
473 @command('kwfiles',
493 @command('kwfiles',
474 [('A', 'all', None, _('show keyword status flags of all files')),
494 [('A', 'all', None, _('show keyword status flags of all files')),
475 ('i', 'ignore', None, _('show files excluded from expansion')),
495 ('i', 'ignore', None, _('show files excluded from expansion')),
476 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
496 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
477 ] + commands.walkopts,
497 ] + commands.walkopts,
478 _('hg kwfiles [OPTION]... [FILE]...'),
498 _('hg kwfiles [OPTION]... [FILE]...'),
479 inferrepo=True)
499 inferrepo=True)
480 def files(ui, repo, *pats, **opts):
500 def files(ui, repo, *pats, **opts):
481 '''show files configured for keyword expansion
501 '''show files configured for keyword expansion
482
502
483 List which files in the working directory are matched by the
503 List which files in the working directory are matched by the
484 [keyword] configuration patterns.
504 [keyword] configuration patterns.
485
505
486 Useful to prevent inadvertent keyword expansion and to speed up
506 Useful to prevent inadvertent keyword expansion and to speed up
487 execution by including only files that are actual candidates for
507 execution by including only files that are actual candidates for
488 expansion.
508 expansion.
489
509
490 See :hg:`help keyword` on how to construct patterns both for
510 See :hg:`help keyword` on how to construct patterns both for
491 inclusion and exclusion of files.
511 inclusion and exclusion of files.
492
512
493 With -A/--all and -v/--verbose the codes used to show the status
513 With -A/--all and -v/--verbose the codes used to show the status
494 of files are::
514 of files are::
495
515
496 K = keyword expansion candidate
516 K = keyword expansion candidate
497 k = keyword expansion candidate (not tracked)
517 k = keyword expansion candidate (not tracked)
498 I = ignored
518 I = ignored
499 i = ignored (not tracked)
519 i = ignored (not tracked)
500 '''
520 '''
501 kwt = kwtools['templater']
521 kwt = kwtools['templater']
502 wctx = repo[None]
522 wctx = repo[None]
503 status = _status(ui, repo, wctx, kwt, *pats, **opts)
523 status = _status(ui, repo, wctx, kwt, *pats, **opts)
504 if pats:
524 if pats:
505 cwd = repo.getcwd()
525 cwd = repo.getcwd()
506 else:
526 else:
507 cwd = ''
527 cwd = ''
508 files = []
528 files = []
509 if not opts.get('unknown') or opts.get('all'):
529 if not opts.get('unknown') or opts.get('all'):
510 files = sorted(status.modified + status.added + status.clean)
530 files = sorted(status.modified + status.added + status.clean)
511 kwfiles = kwt.iskwfile(files, wctx)
531 kwfiles = kwt.iskwfile(files, wctx)
512 kwdeleted = kwt.iskwfile(status.deleted, wctx)
532 kwdeleted = kwt.iskwfile(status.deleted, wctx)
513 kwunknown = kwt.iskwfile(status.unknown, wctx)
533 kwunknown = kwt.iskwfile(status.unknown, wctx)
514 if not opts.get('ignore') or opts.get('all'):
534 if not opts.get('ignore') or opts.get('all'):
515 showfiles = kwfiles, kwdeleted, kwunknown
535 showfiles = kwfiles, kwdeleted, kwunknown
516 else:
536 else:
517 showfiles = [], [], []
537 showfiles = [], [], []
518 if opts.get('all') or opts.get('ignore'):
538 if opts.get('all') or opts.get('ignore'):
519 showfiles += ([f for f in files if f not in kwfiles],
539 showfiles += ([f for f in files if f not in kwfiles],
520 [f for f in status.unknown if f not in kwunknown])
540 [f for f in status.unknown if f not in kwunknown])
521 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
541 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
522 kwstates = zip(kwlabels, 'K!kIi', showfiles)
542 kwstates = zip(kwlabels, 'K!kIi', showfiles)
523 fm = ui.formatter('kwfiles', opts)
543 fm = ui.formatter('kwfiles', opts)
524 fmt = '%.0s%s\n'
544 fmt = '%.0s%s\n'
525 if opts.get('all') or ui.verbose:
545 if opts.get('all') or ui.verbose:
526 fmt = '%s %s\n'
546 fmt = '%s %s\n'
527 for kwstate, char, filenames in kwstates:
547 for kwstate, char, filenames in kwstates:
528 label = 'kwfiles.' + kwstate
548 label = 'kwfiles.' + kwstate
529 for f in filenames:
549 for f in filenames:
530 fm.startitem()
550 fm.startitem()
531 fm.write('kwstatus path', fmt, char,
551 fm.write('kwstatus path', fmt, char,
532 repo.pathto(f, cwd), label=label)
552 repo.pathto(f, cwd), label=label)
533 fm.end()
553 fm.end()
534
554
535 @command('kwshrink',
555 @command('kwshrink',
536 commands.walkopts,
556 commands.walkopts,
537 _('hg kwshrink [OPTION]... [FILE]...'),
557 _('hg kwshrink [OPTION]... [FILE]...'),
538 inferrepo=True)
558 inferrepo=True)
539 def shrink(ui, repo, *pats, **opts):
559 def shrink(ui, repo, *pats, **opts):
540 '''revert expanded keywords in the working directory
560 '''revert expanded keywords in the working directory
541
561
542 Must be run before changing/disabling active keywords.
562 Must be run before changing/disabling active keywords.
543
563
544 kwshrink refuses to run if given files contain local changes.
564 kwshrink refuses to run if given files contain local changes.
545 '''
565 '''
546 # 3rd argument sets expansion to False
566 # 3rd argument sets expansion to False
547 _kwfwrite(ui, repo, False, *pats, **opts)
567 _kwfwrite(ui, repo, False, *pats, **opts)
548
568
549
569
550 def uisetup(ui):
570 def uisetup(ui):
551 ''' Monkeypatches dispatch._parse to retrieve user command.'''
571 ''' Monkeypatches dispatch._parse to retrieve user command.'''
552
572
553 def kwdispatch_parse(orig, ui, args):
573 def kwdispatch_parse(orig, ui, args):
554 '''Monkeypatch dispatch._parse to obtain running hg command.'''
574 '''Monkeypatch dispatch._parse to obtain running hg command.'''
555 cmd, func, args, options, cmdoptions = orig(ui, args)
575 cmd, func, args, options, cmdoptions = orig(ui, args)
556 kwtools['hgcmd'] = cmd
576 kwtools['hgcmd'] = cmd
557 return cmd, func, args, options, cmdoptions
577 return cmd, func, args, options, cmdoptions
558
578
559 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
579 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
560
580
561 def reposetup(ui, repo):
581 def reposetup(ui, repo):
562 '''Sets up repo as kwrepo for keyword substitution.
582 '''Sets up repo as kwrepo for keyword substitution.
563 Overrides file method to return kwfilelog instead of filelog
583 Overrides file method to return kwfilelog instead of filelog
564 if file matches user configuration.
584 if file matches user configuration.
565 Wraps commit to overwrite configured files with updated
585 Wraps commit to overwrite configured files with updated
566 keyword substitutions.
586 keyword substitutions.
567 Monkeypatches patch and webcommands.'''
587 Monkeypatches patch and webcommands.'''
568
588
569 try:
589 try:
570 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
590 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
571 or '.hg' in util.splitpath(repo.root)
591 or '.hg' in util.splitpath(repo.root)
572 or repo._url.startswith('bundle:')):
592 or repo._url.startswith('bundle:')):
573 return
593 return
574 except AttributeError:
594 except AttributeError:
575 pass
595 pass
576
596
577 inc, exc = [], ['.hg*']
597 inc, exc = [], ['.hg*']
578 for pat, opt in ui.configitems('keyword'):
598 for pat, opt in ui.configitems('keyword'):
579 if opt != 'ignore':
599 if opt != 'ignore':
580 inc.append(pat)
600 inc.append(pat)
581 else:
601 else:
582 exc.append(pat)
602 exc.append(pat)
583 if not inc:
603 if not inc:
584 return
604 return
585
605
586 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
606 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
587
607
588 class kwrepo(repo.__class__):
608 class kwrepo(repo.__class__):
589 def file(self, f):
609 def file(self, f):
590 if f[0] == '/':
610 if f[0] == '/':
591 f = f[1:]
611 f = f[1:]
592 return kwfilelog(self.svfs, kwt, f)
612 return kwfilelog(self.svfs, kwt, f)
593
613
594 def wread(self, filename):
614 def wread(self, filename):
595 data = super(kwrepo, self).wread(filename)
615 data = super(kwrepo, self).wread(filename)
596 return kwt.wread(filename, data)
616 return kwt.wread(filename, data)
597
617
598 def commit(self, *args, **opts):
618 def commit(self, *args, **opts):
599 # use custom commitctx for user commands
619 # use custom commitctx for user commands
600 # other extensions can still wrap repo.commitctx directly
620 # other extensions can still wrap repo.commitctx directly
601 self.commitctx = self.kwcommitctx
621 self.commitctx = self.kwcommitctx
602 try:
622 try:
603 return super(kwrepo, self).commit(*args, **opts)
623 return super(kwrepo, self).commit(*args, **opts)
604 finally:
624 finally:
605 del self.commitctx
625 del self.commitctx
606
626
607 def kwcommitctx(self, ctx, error=False):
627 def kwcommitctx(self, ctx, error=False):
608 n = super(kwrepo, self).commitctx(ctx, error)
628 n = super(kwrepo, self).commitctx(ctx, error)
609 # no lock needed, only called from repo.commit() which already locks
629 # no lock needed, only called from repo.commit() which already locks
610 if not kwt.postcommit:
630 if not kwt.postcommit:
611 restrict = kwt.restrict
631 restrict = kwt.restrict
612 kwt.restrict = True
632 kwt.restrict = True
613 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
633 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
614 False, True)
634 False, True)
615 kwt.restrict = restrict
635 kwt.restrict = restrict
616 return n
636 return n
617
637
618 def rollback(self, dryrun=False, force=False):
638 def rollback(self, dryrun=False, force=False):
619 wlock = self.wlock()
639 wlock = self.wlock()
620 origrestrict = kwt.restrict
640 origrestrict = kwt.restrict
621 try:
641 try:
622 if not dryrun:
642 if not dryrun:
623 changed = self['.'].files()
643 changed = self['.'].files()
624 ret = super(kwrepo, self).rollback(dryrun, force)
644 ret = super(kwrepo, self).rollback(dryrun, force)
625 if not dryrun:
645 if not dryrun:
626 ctx = self['.']
646 ctx = self['.']
627 modified, added = _preselect(ctx.status(), changed)
647 modified, added = _preselect(ctx.status(), changed)
628 kwt.restrict = False
648 kwt.restrict = False
629 kwt.overwrite(ctx, modified, True, True)
649 kwt.overwrite(ctx, modified, True, True)
630 kwt.overwrite(ctx, added, True, False)
650 kwt.overwrite(ctx, added, True, False)
631 return ret
651 return ret
632 finally:
652 finally:
633 kwt.restrict = origrestrict
653 kwt.restrict = origrestrict
634 wlock.release()
654 wlock.release()
635
655
636 # monkeypatches
656 # monkeypatches
637 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
657 def kwpatchfile_init(orig, self, ui, gp, backend, store, eolmode=None):
638 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
658 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
639 rejects or conflicts due to expanded keywords in working dir.'''
659 rejects or conflicts due to expanded keywords in working dir.'''
640 orig(self, ui, gp, backend, store, eolmode)
660 orig(self, ui, gp, backend, store, eolmode)
641 # shrink keywords read from working dir
661 # shrink keywords read from working dir
642 self.lines = kwt.shrinklines(self.fname, self.lines)
662 self.lines = kwt.shrinklines(self.fname, self.lines)
643
663
644 def kwdiff(orig, *args, **kwargs):
664 def kwdiff(orig, *args, **kwargs):
645 '''Monkeypatch patch.diff to avoid expansion.'''
665 '''Monkeypatch patch.diff to avoid expansion.'''
646 kwt.restrict = True
666 kwt.restrict = True
647 return orig(*args, **kwargs)
667 return orig(*args, **kwargs)
648
668
649 def kwweb_skip(orig, web, req, tmpl):
669 def kwweb_skip(orig, web, req, tmpl):
650 '''Wraps webcommands.x turning off keyword expansion.'''
670 '''Wraps webcommands.x turning off keyword expansion.'''
651 kwt.match = util.never
671 kwt.match = util.never
652 return orig(web, req, tmpl)
672 return orig(web, req, tmpl)
653
673
654 def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
674 def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
655 '''Wraps cmdutil.amend expanding keywords after amend.'''
675 '''Wraps cmdutil.amend expanding keywords after amend.'''
656 with repo.wlock():
676 with repo.wlock():
657 kwt.postcommit = True
677 kwt.postcommit = True
658 newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
678 newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
659 if newid != old.node():
679 if newid != old.node():
660 ctx = repo[newid]
680 ctx = repo[newid]
661 kwt.restrict = True
681 kwt.restrict = True
662 kwt.overwrite(ctx, ctx.files(), False, True)
682 kwt.overwrite(ctx, ctx.files(), False, True)
663 kwt.restrict = False
683 kwt.restrict = False
664 return newid
684 return newid
665
685
666 def kw_copy(orig, ui, repo, pats, opts, rename=False):
686 def kw_copy(orig, ui, repo, pats, opts, rename=False):
667 '''Wraps cmdutil.copy so that copy/rename destinations do not
687 '''Wraps cmdutil.copy so that copy/rename destinations do not
668 contain expanded keywords.
688 contain expanded keywords.
669 Note that the source of a regular file destination may also be a
689 Note that the source of a regular file destination may also be a
670 symlink:
690 symlink:
671 hg cp sym x -> x is symlink
691 hg cp sym x -> x is symlink
672 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
692 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
673 For the latter we have to follow the symlink to find out whether its
693 For the latter we have to follow the symlink to find out whether its
674 target is configured for expansion and we therefore must unexpand the
694 target is configured for expansion and we therefore must unexpand the
675 keywords in the destination.'''
695 keywords in the destination.'''
676 with repo.wlock():
696 with repo.wlock():
677 orig(ui, repo, pats, opts, rename)
697 orig(ui, repo, pats, opts, rename)
678 if opts.get('dry_run'):
698 if opts.get('dry_run'):
679 return
699 return
680 wctx = repo[None]
700 wctx = repo[None]
681 cwd = repo.getcwd()
701 cwd = repo.getcwd()
682
702
683 def haskwsource(dest):
703 def haskwsource(dest):
684 '''Returns true if dest is a regular file and configured for
704 '''Returns true if dest is a regular file and configured for
685 expansion or a symlink which points to a file configured for
705 expansion or a symlink which points to a file configured for
686 expansion. '''
706 expansion. '''
687 source = repo.dirstate.copied(dest)
707 source = repo.dirstate.copied(dest)
688 if 'l' in wctx.flags(source):
708 if 'l' in wctx.flags(source):
689 source = pathutil.canonpath(repo.root, cwd,
709 source = pathutil.canonpath(repo.root, cwd,
690 os.path.realpath(source))
710 os.path.realpath(source))
691 return kwt.match(source)
711 return kwt.match(source)
692
712
693 candidates = [f for f in repo.dirstate.copies() if
713 candidates = [f for f in repo.dirstate.copies() if
694 'l' not in wctx.flags(f) and haskwsource(f)]
714 'l' not in wctx.flags(f) and haskwsource(f)]
695 kwt.overwrite(wctx, candidates, False, False)
715 kwt.overwrite(wctx, candidates, False, False)
696
716
697 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
717 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
698 '''Wraps record.dorecord expanding keywords after recording.'''
718 '''Wraps record.dorecord expanding keywords after recording.'''
699 with repo.wlock():
719 with repo.wlock():
700 # record returns 0 even when nothing has changed
720 # record returns 0 even when nothing has changed
701 # therefore compare nodes before and after
721 # therefore compare nodes before and after
702 kwt.postcommit = True
722 kwt.postcommit = True
703 ctx = repo['.']
723 ctx = repo['.']
704 wstatus = ctx.status()
724 wstatus = ctx.status()
705 ret = orig(ui, repo, commitfunc, *pats, **opts)
725 ret = orig(ui, repo, commitfunc, *pats, **opts)
706 recctx = repo['.']
726 recctx = repo['.']
707 if ctx != recctx:
727 if ctx != recctx:
708 modified, added = _preselect(wstatus, recctx.files())
728 modified, added = _preselect(wstatus, recctx.files())
709 kwt.restrict = False
729 kwt.restrict = False
710 kwt.overwrite(recctx, modified, False, True)
730 kwt.overwrite(recctx, modified, False, True)
711 kwt.overwrite(recctx, added, False, True, True)
731 kwt.overwrite(recctx, added, False, True, True)
712 kwt.restrict = True
732 kwt.restrict = True
713 return ret
733 return ret
714
734
715 def kwfilectx_cmp(orig, self, fctx):
735 def kwfilectx_cmp(orig, self, fctx):
716 # keyword affects data size, comparing wdir and filelog size does
736 # keyword affects data size, comparing wdir and filelog size does
717 # not make sense
737 # not make sense
718 if (fctx._filerev is None and
738 if (fctx._filerev is None and
719 (self._repo._encodefilterpats or
739 (self._repo._encodefilterpats or
720 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
740 kwt.match(fctx.path()) and 'l' not in fctx.flags() or
721 self.size() - 4 == fctx.size()) or
741 self.size() - 4 == fctx.size()) or
722 self.size() == fctx.size()):
742 self.size() == fctx.size()):
723 return self._filelog.cmp(self._filenode, fctx.data())
743 return self._filelog.cmp(self._filenode, fctx.data())
724 return True
744 return True
725
745
726 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
746 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
727 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
747 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
728 extensions.wrapfunction(patch, 'diff', kwdiff)
748 extensions.wrapfunction(patch, 'diff', kwdiff)
729 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
749 extensions.wrapfunction(cmdutil, 'amend', kw_amend)
730 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
750 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
731 extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord)
751 extensions.wrapfunction(cmdutil, 'dorecord', kw_dorecord)
732 for c in 'annotate changeset rev filediff diff'.split():
752 for c in 'annotate changeset rev filediff diff'.split():
733 extensions.wrapfunction(webcommands, c, kwweb_skip)
753 extensions.wrapfunction(webcommands, c, kwweb_skip)
734 repo.__class__ = kwrepo
754 repo.__class__ = kwrepo
@@ -1,170 +1,169 b''
1 #require test-repo
1 #require test-repo
2
2
3 $ cd "$TESTDIR"/..
3 $ cd "$TESTDIR"/..
4
4
5 $ hg files 'set:(**.py)' | sed 's|\\|/|g' | xargs python contrib/check-py3-compat.py
5 $ hg files 'set:(**.py)' | sed 's|\\|/|g' | xargs python contrib/check-py3-compat.py
6 contrib/casesmash.py not using absolute_import
6 contrib/casesmash.py not using absolute_import
7 contrib/check-code.py not using absolute_import
7 contrib/check-code.py not using absolute_import
8 contrib/check-code.py requires print_function
8 contrib/check-code.py requires print_function
9 contrib/check-config.py not using absolute_import
9 contrib/check-config.py not using absolute_import
10 contrib/check-config.py requires print_function
10 contrib/check-config.py requires print_function
11 contrib/debugcmdserver.py not using absolute_import
11 contrib/debugcmdserver.py not using absolute_import
12 contrib/debugcmdserver.py requires print_function
12 contrib/debugcmdserver.py requires print_function
13 contrib/debugshell.py not using absolute_import
13 contrib/debugshell.py not using absolute_import
14 contrib/fixpax.py not using absolute_import
14 contrib/fixpax.py not using absolute_import
15 contrib/fixpax.py requires print_function
15 contrib/fixpax.py requires print_function
16 contrib/hgclient.py not using absolute_import
16 contrib/hgclient.py not using absolute_import
17 contrib/hgclient.py requires print_function
17 contrib/hgclient.py requires print_function
18 contrib/hgfixes/fix_bytes.py not using absolute_import
18 contrib/hgfixes/fix_bytes.py not using absolute_import
19 contrib/hgfixes/fix_bytesmod.py not using absolute_import
19 contrib/hgfixes/fix_bytesmod.py not using absolute_import
20 contrib/hgfixes/fix_leftover_imports.py not using absolute_import
20 contrib/hgfixes/fix_leftover_imports.py not using absolute_import
21 contrib/import-checker.py not using absolute_import
21 contrib/import-checker.py not using absolute_import
22 contrib/import-checker.py requires print_function
22 contrib/import-checker.py requires print_function
23 contrib/memory.py not using absolute_import
23 contrib/memory.py not using absolute_import
24 contrib/perf.py not using absolute_import
24 contrib/perf.py not using absolute_import
25 contrib/python-hook-examples.py not using absolute_import
25 contrib/python-hook-examples.py not using absolute_import
26 contrib/revsetbenchmarks.py not using absolute_import
26 contrib/revsetbenchmarks.py not using absolute_import
27 contrib/revsetbenchmarks.py requires print_function
27 contrib/revsetbenchmarks.py requires print_function
28 contrib/showstack.py not using absolute_import
28 contrib/showstack.py not using absolute_import
29 contrib/synthrepo.py not using absolute_import
29 contrib/synthrepo.py not using absolute_import
30 contrib/win32/hgwebdir_wsgi.py not using absolute_import
30 contrib/win32/hgwebdir_wsgi.py not using absolute_import
31 doc/check-seclevel.py not using absolute_import
31 doc/check-seclevel.py not using absolute_import
32 doc/gendoc.py not using absolute_import
32 doc/gendoc.py not using absolute_import
33 doc/hgmanpage.py not using absolute_import
33 doc/hgmanpage.py not using absolute_import
34 hgext/__init__.py not using absolute_import
34 hgext/__init__.py not using absolute_import
35 hgext/color.py not using absolute_import
35 hgext/color.py not using absolute_import
36 hgext/convert/__init__.py not using absolute_import
36 hgext/convert/__init__.py not using absolute_import
37 hgext/convert/bzr.py not using absolute_import
37 hgext/convert/bzr.py not using absolute_import
38 hgext/convert/common.py not using absolute_import
38 hgext/convert/common.py not using absolute_import
39 hgext/convert/convcmd.py not using absolute_import
39 hgext/convert/convcmd.py not using absolute_import
40 hgext/convert/cvs.py not using absolute_import
40 hgext/convert/cvs.py not using absolute_import
41 hgext/convert/cvsps.py not using absolute_import
41 hgext/convert/cvsps.py not using absolute_import
42 hgext/convert/darcs.py not using absolute_import
42 hgext/convert/darcs.py not using absolute_import
43 hgext/convert/filemap.py not using absolute_import
43 hgext/convert/filemap.py not using absolute_import
44 hgext/convert/git.py not using absolute_import
44 hgext/convert/git.py not using absolute_import
45 hgext/convert/gnuarch.py not using absolute_import
45 hgext/convert/gnuarch.py not using absolute_import
46 hgext/convert/hg.py not using absolute_import
46 hgext/convert/hg.py not using absolute_import
47 hgext/convert/monotone.py not using absolute_import
47 hgext/convert/monotone.py not using absolute_import
48 hgext/convert/p4.py not using absolute_import
48 hgext/convert/p4.py not using absolute_import
49 hgext/convert/subversion.py not using absolute_import
49 hgext/convert/subversion.py not using absolute_import
50 hgext/convert/transport.py not using absolute_import
50 hgext/convert/transport.py not using absolute_import
51 hgext/eol.py not using absolute_import
51 hgext/eol.py not using absolute_import
52 hgext/extdiff.py not using absolute_import
52 hgext/extdiff.py not using absolute_import
53 hgext/factotum.py not using absolute_import
53 hgext/factotum.py not using absolute_import
54 hgext/fetch.py not using absolute_import
54 hgext/fetch.py not using absolute_import
55 hgext/gpg.py not using absolute_import
55 hgext/gpg.py not using absolute_import
56 hgext/graphlog.py not using absolute_import
56 hgext/graphlog.py not using absolute_import
57 hgext/hgcia.py not using absolute_import
57 hgext/hgcia.py not using absolute_import
58 hgext/hgk.py not using absolute_import
58 hgext/hgk.py not using absolute_import
59 hgext/highlight/__init__.py not using absolute_import
59 hgext/highlight/__init__.py not using absolute_import
60 hgext/highlight/highlight.py not using absolute_import
60 hgext/highlight/highlight.py not using absolute_import
61 hgext/histedit.py not using absolute_import
61 hgext/histedit.py not using absolute_import
62 hgext/keyword.py not using absolute_import
63 hgext/largefiles/__init__.py not using absolute_import
62 hgext/largefiles/__init__.py not using absolute_import
64 hgext/largefiles/basestore.py not using absolute_import
63 hgext/largefiles/basestore.py not using absolute_import
65 hgext/largefiles/lfcommands.py not using absolute_import
64 hgext/largefiles/lfcommands.py not using absolute_import
66 hgext/largefiles/lfutil.py not using absolute_import
65 hgext/largefiles/lfutil.py not using absolute_import
67 hgext/largefiles/localstore.py not using absolute_import
66 hgext/largefiles/localstore.py not using absolute_import
68 hgext/largefiles/overrides.py not using absolute_import
67 hgext/largefiles/overrides.py not using absolute_import
69 hgext/largefiles/proto.py not using absolute_import
68 hgext/largefiles/proto.py not using absolute_import
70 hgext/largefiles/remotestore.py not using absolute_import
69 hgext/largefiles/remotestore.py not using absolute_import
71 hgext/largefiles/reposetup.py not using absolute_import
70 hgext/largefiles/reposetup.py not using absolute_import
72 hgext/largefiles/uisetup.py not using absolute_import
71 hgext/largefiles/uisetup.py not using absolute_import
73 hgext/largefiles/wirestore.py not using absolute_import
72 hgext/largefiles/wirestore.py not using absolute_import
74 hgext/mq.py not using absolute_import
73 hgext/mq.py not using absolute_import
75 hgext/notify.py not using absolute_import
74 hgext/notify.py not using absolute_import
76 hgext/patchbomb.py not using absolute_import
75 hgext/patchbomb.py not using absolute_import
77 hgext/purge.py not using absolute_import
76 hgext/purge.py not using absolute_import
78 hgext/rebase.py not using absolute_import
77 hgext/rebase.py not using absolute_import
79 hgext/record.py not using absolute_import
78 hgext/record.py not using absolute_import
80 hgext/relink.py not using absolute_import
79 hgext/relink.py not using absolute_import
81 hgext/schemes.py not using absolute_import
80 hgext/schemes.py not using absolute_import
82 hgext/share.py not using absolute_import
81 hgext/share.py not using absolute_import
83 hgext/shelve.py not using absolute_import
82 hgext/shelve.py not using absolute_import
84 hgext/strip.py not using absolute_import
83 hgext/strip.py not using absolute_import
85 hgext/transplant.py not using absolute_import
84 hgext/transplant.py not using absolute_import
86 hgext/win32mbcs.py not using absolute_import
85 hgext/win32mbcs.py not using absolute_import
87 hgext/win32text.py not using absolute_import
86 hgext/win32text.py not using absolute_import
88 i18n/check-translation.py not using absolute_import
87 i18n/check-translation.py not using absolute_import
89 i18n/polib.py not using absolute_import
88 i18n/polib.py not using absolute_import
90 mercurial/cmdutil.py not using absolute_import
89 mercurial/cmdutil.py not using absolute_import
91 mercurial/commands.py not using absolute_import
90 mercurial/commands.py not using absolute_import
92 setup.py not using absolute_import
91 setup.py not using absolute_import
93 tests/filterpyflakes.py requires print_function
92 tests/filterpyflakes.py requires print_function
94 tests/generate-working-copy-states.py requires print_function
93 tests/generate-working-copy-states.py requires print_function
95 tests/get-with-headers.py requires print_function
94 tests/get-with-headers.py requires print_function
96 tests/heredoctest.py requires print_function
95 tests/heredoctest.py requires print_function
97 tests/hypothesishelpers.py not using absolute_import
96 tests/hypothesishelpers.py not using absolute_import
98 tests/hypothesishelpers.py requires print_function
97 tests/hypothesishelpers.py requires print_function
99 tests/killdaemons.py not using absolute_import
98 tests/killdaemons.py not using absolute_import
100 tests/md5sum.py not using absolute_import
99 tests/md5sum.py not using absolute_import
101 tests/mockblackbox.py not using absolute_import
100 tests/mockblackbox.py not using absolute_import
102 tests/printenv.py not using absolute_import
101 tests/printenv.py not using absolute_import
103 tests/readlink.py not using absolute_import
102 tests/readlink.py not using absolute_import
104 tests/readlink.py requires print_function
103 tests/readlink.py requires print_function
105 tests/revlog-formatv0.py not using absolute_import
104 tests/revlog-formatv0.py not using absolute_import
106 tests/run-tests.py not using absolute_import
105 tests/run-tests.py not using absolute_import
107 tests/seq.py not using absolute_import
106 tests/seq.py not using absolute_import
108 tests/seq.py requires print_function
107 tests/seq.py requires print_function
109 tests/silenttestrunner.py not using absolute_import
108 tests/silenttestrunner.py not using absolute_import
110 tests/silenttestrunner.py requires print_function
109 tests/silenttestrunner.py requires print_function
111 tests/sitecustomize.py not using absolute_import
110 tests/sitecustomize.py not using absolute_import
112 tests/svn-safe-append.py not using absolute_import
111 tests/svn-safe-append.py not using absolute_import
113 tests/svnxml.py not using absolute_import
112 tests/svnxml.py not using absolute_import
114 tests/test-ancestor.py requires print_function
113 tests/test-ancestor.py requires print_function
115 tests/test-atomictempfile.py not using absolute_import
114 tests/test-atomictempfile.py not using absolute_import
116 tests/test-batching.py not using absolute_import
115 tests/test-batching.py not using absolute_import
117 tests/test-batching.py requires print_function
116 tests/test-batching.py requires print_function
118 tests/test-bdiff.py not using absolute_import
117 tests/test-bdiff.py not using absolute_import
119 tests/test-bdiff.py requires print_function
118 tests/test-bdiff.py requires print_function
120 tests/test-context.py not using absolute_import
119 tests/test-context.py not using absolute_import
121 tests/test-context.py requires print_function
120 tests/test-context.py requires print_function
122 tests/test-demandimport.py not using absolute_import
121 tests/test-demandimport.py not using absolute_import
123 tests/test-demandimport.py requires print_function
122 tests/test-demandimport.py requires print_function
124 tests/test-dispatch.py not using absolute_import
123 tests/test-dispatch.py not using absolute_import
125 tests/test-dispatch.py requires print_function
124 tests/test-dispatch.py requires print_function
126 tests/test-doctest.py not using absolute_import
125 tests/test-doctest.py not using absolute_import
127 tests/test-duplicateoptions.py not using absolute_import
126 tests/test-duplicateoptions.py not using absolute_import
128 tests/test-duplicateoptions.py requires print_function
127 tests/test-duplicateoptions.py requires print_function
129 tests/test-filecache.py not using absolute_import
128 tests/test-filecache.py not using absolute_import
130 tests/test-filecache.py requires print_function
129 tests/test-filecache.py requires print_function
131 tests/test-filelog.py not using absolute_import
130 tests/test-filelog.py not using absolute_import
132 tests/test-filelog.py requires print_function
131 tests/test-filelog.py requires print_function
133 tests/test-hg-parseurl.py not using absolute_import
132 tests/test-hg-parseurl.py not using absolute_import
134 tests/test-hg-parseurl.py requires print_function
133 tests/test-hg-parseurl.py requires print_function
135 tests/test-hgweb-auth.py not using absolute_import
134 tests/test-hgweb-auth.py not using absolute_import
136 tests/test-hgweb-auth.py requires print_function
135 tests/test-hgweb-auth.py requires print_function
137 tests/test-hgwebdir-paths.py not using absolute_import
136 tests/test-hgwebdir-paths.py not using absolute_import
138 tests/test-hybridencode.py not using absolute_import
137 tests/test-hybridencode.py not using absolute_import
139 tests/test-hybridencode.py requires print_function
138 tests/test-hybridencode.py requires print_function
140 tests/test-lrucachedict.py not using absolute_import
139 tests/test-lrucachedict.py not using absolute_import
141 tests/test-lrucachedict.py requires print_function
140 tests/test-lrucachedict.py requires print_function
142 tests/test-manifest.py not using absolute_import
141 tests/test-manifest.py not using absolute_import
143 tests/test-minirst.py not using absolute_import
142 tests/test-minirst.py not using absolute_import
144 tests/test-minirst.py requires print_function
143 tests/test-minirst.py requires print_function
145 tests/test-parseindex2.py not using absolute_import
144 tests/test-parseindex2.py not using absolute_import
146 tests/test-parseindex2.py requires print_function
145 tests/test-parseindex2.py requires print_function
147 tests/test-pathencode.py not using absolute_import
146 tests/test-pathencode.py not using absolute_import
148 tests/test-pathencode.py requires print_function
147 tests/test-pathencode.py requires print_function
149 tests/test-propertycache.py not using absolute_import
148 tests/test-propertycache.py not using absolute_import
150 tests/test-propertycache.py requires print_function
149 tests/test-propertycache.py requires print_function
151 tests/test-revlog-ancestry.py not using absolute_import
150 tests/test-revlog-ancestry.py not using absolute_import
152 tests/test-revlog-ancestry.py requires print_function
151 tests/test-revlog-ancestry.py requires print_function
153 tests/test-run-tests.py not using absolute_import
152 tests/test-run-tests.py not using absolute_import
154 tests/test-simplemerge.py not using absolute_import
153 tests/test-simplemerge.py not using absolute_import
155 tests/test-status-inprocess.py not using absolute_import
154 tests/test-status-inprocess.py not using absolute_import
156 tests/test-status-inprocess.py requires print_function
155 tests/test-status-inprocess.py requires print_function
157 tests/test-symlink-os-yes-fs-no.py not using absolute_import
156 tests/test-symlink-os-yes-fs-no.py not using absolute_import
158 tests/test-trusted.py not using absolute_import
157 tests/test-trusted.py not using absolute_import
159 tests/test-trusted.py requires print_function
158 tests/test-trusted.py requires print_function
160 tests/test-ui-color.py not using absolute_import
159 tests/test-ui-color.py not using absolute_import
161 tests/test-ui-color.py requires print_function
160 tests/test-ui-color.py requires print_function
162 tests/test-ui-config.py not using absolute_import
161 tests/test-ui-config.py not using absolute_import
163 tests/test-ui-config.py requires print_function
162 tests/test-ui-config.py requires print_function
164 tests/test-ui-verbosity.py not using absolute_import
163 tests/test-ui-verbosity.py not using absolute_import
165 tests/test-ui-verbosity.py requires print_function
164 tests/test-ui-verbosity.py requires print_function
166 tests/test-url.py not using absolute_import
165 tests/test-url.py not using absolute_import
167 tests/test-url.py requires print_function
166 tests/test-url.py requires print_function
168 tests/test-walkrepo.py requires print_function
167 tests/test-walkrepo.py requires print_function
169 tests/test-wireproto.py requires print_function
168 tests/test-wireproto.py requires print_function
170 tests/tinyproxy.py requires print_function
169 tests/tinyproxy.py requires print_function
General Comments 0
You need to be logged in to leave comments. Login now