##// END OF EJS Templates
keyword: update documentation for kwshrink...
Christian Ebert -
r13270:1e0d631b default
parent child Browse files
Show More
@@ -1,674 +1,672 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007-2010 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007-2010 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 DSCM
10 # Keyword expansion hack against the grain of a DSCM
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 # <http://mercurial.selenic.com/wiki/KeywordPlan>.
18 # <http://mercurial.selenic.com/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 The more specific you are in your filename patterns the less you
56 The more specific you are in your filename patterns the less you
57 lose speed in huge repositories.
57 lose speed in huge repositories.
58
58
59 For [keywordmaps] template mapping and expansion demonstration and
59 For [keywordmaps] template mapping and expansion demonstration and
60 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
60 control run :hg:`kwdemo`. See :hg:`help templates` for a list of
61 available templates and filters.
61 available templates and filters.
62
62
63 Three additional date template filters are provided::
63 Three additional date template filters are provided::
64
64
65 utcdate "2006/09/18 15:13:13"
65 utcdate "2006/09/18 15:13:13"
66 svnutcdate "2006-09-18 15:13:13Z"
66 svnutcdate "2006-09-18 15:13:13Z"
67 svnisodate "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
67 svnisodate "2006-09-18 08:13:13 -700 (Mon, 18 Sep 2006)"
68
68
69 The default template mappings (view with :hg:`kwdemo -d`) can be
69 The default template mappings (view with :hg:`kwdemo -d`) can be
70 replaced with customized keywords and templates. Again, run
70 replaced with customized keywords and templates. Again, run
71 :hg:`kwdemo` to control the results of your configuration changes.
71 :hg:`kwdemo` to control the results of your configuration changes.
72
72
73 Before changing/disabling active keywords, run :hg:`kwshrink` to avoid
73 Before changing/disabling active keywords, you must run :hg:`kwshrink`
74 the risk of inadvertently storing expanded keywords in the change
74 to avoid storing expanded keywords in the change history.
75 history.
76
75
77 To force expansion after enabling it, or a configuration change, run
76 To force expansion after enabling it, or a configuration change, run
78 :hg:`kwexpand`.
77 :hg:`kwexpand`.
79
78
80 Expansions spanning more than one line and incremental expansions,
79 Expansions spanning more than one line and incremental expansions,
81 like CVS' $Log$, are not supported. A keyword template map "Log =
80 like CVS' $Log$, are not supported. A keyword template map "Log =
82 {desc}" expands to the first line of the changeset description.
81 {desc}" expands to the first line of the changeset description.
83 '''
82 '''
84
83
85 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
84 from mercurial import commands, context, cmdutil, dispatch, filelog, extensions
86 from mercurial import localrepo, match, patch, templatefilters, templater, util
85 from mercurial import localrepo, match, patch, templatefilters, templater, util
87 from mercurial.hgweb import webcommands
86 from mercurial.hgweb import webcommands
88 from mercurial.i18n import _
87 from mercurial.i18n import _
89 import os, re, shutil, tempfile
88 import os, re, shutil, tempfile
90
89
91 commands.optionalrepo += ' kwdemo'
90 commands.optionalrepo += ' kwdemo'
92
91
93 # hg commands that do not act on keywords
92 # hg commands that do not act on keywords
94 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
93 nokwcommands = ('add addremove annotate bundle export grep incoming init log'
95 ' outgoing push tip verify convert email glog')
94 ' outgoing push tip verify convert email glog')
96
95
97 # hg commands that trigger expansion only when writing to working dir,
96 # hg commands that trigger expansion only when writing to working dir,
98 # not when reading filelog, and unexpand when reading from working dir
97 # not when reading filelog, and unexpand when reading from working dir
99 restricted = 'merge kwexpand kwshrink record qrecord resolve transplant'
98 restricted = 'merge kwexpand kwshrink record qrecord resolve transplant'
100
99
101 # names of extensions using dorecord
100 # names of extensions using dorecord
102 recordextensions = 'record'
101 recordextensions = 'record'
103
102
104 colortable = {
103 colortable = {
105 'kwfiles.enabled': 'green bold',
104 'kwfiles.enabled': 'green bold',
106 'kwfiles.deleted': 'cyan bold underline',
105 'kwfiles.deleted': 'cyan bold underline',
107 'kwfiles.enabledunknown': 'green',
106 'kwfiles.enabledunknown': 'green',
108 'kwfiles.ignored': 'bold',
107 'kwfiles.ignored': 'bold',
109 'kwfiles.ignoredunknown': 'none'
108 'kwfiles.ignoredunknown': 'none'
110 }
109 }
111
110
112 # date like in cvs' $Date
111 # date like in cvs' $Date
113 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
112 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
114 # date like in svn's $Date
113 # date like in svn's $Date
115 svnisodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
114 svnisodate = lambda x: util.datestr(x, '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)')
116 # date like in svn's $Id
115 # date like in svn's $Id
117 svnutcdate = lambda x: util.datestr((x[0], 0), '%Y-%m-%d %H:%M:%SZ')
116 svnutcdate = lambda x: util.datestr((x[0], 0), '%Y-%m-%d %H:%M:%SZ')
118
117
119 # make keyword tools accessible
118 # make keyword tools accessible
120 kwtools = {'templater': None, 'hgcmd': ''}
119 kwtools = {'templater': None, 'hgcmd': ''}
121
120
122 def _defaultkwmaps(ui):
121 def _defaultkwmaps(ui):
123 '''Returns default keywordmaps according to keywordset configuration.'''
122 '''Returns default keywordmaps according to keywordset configuration.'''
124 templates = {
123 templates = {
125 'Revision': '{node|short}',
124 'Revision': '{node|short}',
126 'Author': '{author|user}',
125 'Author': '{author|user}',
127 }
126 }
128 kwsets = ({
127 kwsets = ({
129 'Date': '{date|utcdate}',
128 'Date': '{date|utcdate}',
130 'RCSfile': '{file|basename},v',
129 'RCSfile': '{file|basename},v',
131 'RCSFile': '{file|basename},v', # kept for backwards compatibility
130 'RCSFile': '{file|basename},v', # kept for backwards compatibility
132 # with hg-keyword
131 # with hg-keyword
133 'Source': '{root}/{file},v',
132 'Source': '{root}/{file},v',
134 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
133 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
135 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
134 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
136 }, {
135 }, {
137 'Date': '{date|svnisodate}',
136 'Date': '{date|svnisodate}',
138 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
137 'Id': '{file|basename},v {node|short} {date|svnutcdate} {author|user}',
139 'LastChangedRevision': '{node|short}',
138 'LastChangedRevision': '{node|short}',
140 'LastChangedBy': '{author|user}',
139 'LastChangedBy': '{author|user}',
141 'LastChangedDate': '{date|svnisodate}',
140 'LastChangedDate': '{date|svnisodate}',
142 })
141 })
143 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
142 templates.update(kwsets[ui.configbool('keywordset', 'svn')])
144 return templates
143 return templates
145
144
146 def _shrinktext(text, subfunc):
145 def _shrinktext(text, subfunc):
147 '''Helper for keyword expansion removal in text.
146 '''Helper for keyword expansion removal in text.
148 Depending on subfunc also returns number of substitutions.'''
147 Depending on subfunc also returns number of substitutions.'''
149 return subfunc(r'$\1$', text)
148 return subfunc(r'$\1$', text)
150
149
151 def _preselect(wstatus, changed):
150 def _preselect(wstatus, changed):
152 '''Retrieves modfied and added files from a working directory state
151 '''Retrieves modfied and added files from a working directory state
153 and returns the subset of each contained in given changed files
152 and returns the subset of each contained in given changed files
154 retrieved from a change context.'''
153 retrieved from a change context.'''
155 modified, added = wstatus[:2]
154 modified, added = wstatus[:2]
156 modified = [f for f in modified if f in changed]
155 modified = [f for f in modified if f in changed]
157 added = [f for f in added if f in changed]
156 added = [f for f in added if f in changed]
158 return modified, added
157 return modified, added
159
158
160
159
161 class kwtemplater(object):
160 class kwtemplater(object):
162 '''
161 '''
163 Sets up keyword templates, corresponding keyword regex, and
162 Sets up keyword templates, corresponding keyword regex, and
164 provides keyword substitution functions.
163 provides keyword substitution functions.
165 '''
164 '''
166
165
167 def __init__(self, ui, repo, inc, exc):
166 def __init__(self, ui, repo, inc, exc):
168 self.ui = ui
167 self.ui = ui
169 self.repo = repo
168 self.repo = repo
170 self.match = match.match(repo.root, '', [], inc, exc)
169 self.match = match.match(repo.root, '', [], inc, exc)
171 self.restrict = kwtools['hgcmd'] in restricted.split()
170 self.restrict = kwtools['hgcmd'] in restricted.split()
172 self.record = False
171 self.record = False
173
172
174 kwmaps = self.ui.configitems('keywordmaps')
173 kwmaps = self.ui.configitems('keywordmaps')
175 if kwmaps: # override default templates
174 if kwmaps: # override default templates
176 self.templates = dict((k, templater.parsestring(v, False))
175 self.templates = dict((k, templater.parsestring(v, False))
177 for k, v in kwmaps)
176 for k, v in kwmaps)
178 else:
177 else:
179 self.templates = _defaultkwmaps(self.ui)
178 self.templates = _defaultkwmaps(self.ui)
180 templatefilters.filters.update({'utcdate': utcdate,
179 templatefilters.filters.update({'utcdate': utcdate,
181 'svnisodate': svnisodate,
180 'svnisodate': svnisodate,
182 'svnutcdate': svnutcdate})
181 'svnutcdate': svnutcdate})
183
182
184 @util.propertycache
183 @util.propertycache
185 def escape(self):
184 def escape(self):
186 '''Returns bar-separated and escaped keywords.'''
185 '''Returns bar-separated and escaped keywords.'''
187 return '|'.join(map(re.escape, self.templates.keys()))
186 return '|'.join(map(re.escape, self.templates.keys()))
188
187
189 @util.propertycache
188 @util.propertycache
190 def rekw(self):
189 def rekw(self):
191 '''Returns regex for unexpanded keywords.'''
190 '''Returns regex for unexpanded keywords.'''
192 return re.compile(r'\$(%s)\$' % self.escape)
191 return re.compile(r'\$(%s)\$' % self.escape)
193
192
194 @util.propertycache
193 @util.propertycache
195 def rekwexp(self):
194 def rekwexp(self):
196 '''Returns regex for expanded keywords.'''
195 '''Returns regex for expanded keywords.'''
197 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
196 return re.compile(r'\$(%s): [^$\n\r]*? \$' % self.escape)
198
197
199 def substitute(self, data, path, ctx, subfunc):
198 def substitute(self, data, path, ctx, subfunc):
200 '''Replaces keywords in data with expanded template.'''
199 '''Replaces keywords in data with expanded template.'''
201 def kwsub(mobj):
200 def kwsub(mobj):
202 kw = mobj.group(1)
201 kw = mobj.group(1)
203 ct = cmdutil.changeset_templater(self.ui, self.repo,
202 ct = cmdutil.changeset_templater(self.ui, self.repo,
204 False, None, '', False)
203 False, None, '', False)
205 ct.use_template(self.templates[kw])
204 ct.use_template(self.templates[kw])
206 self.ui.pushbuffer()
205 self.ui.pushbuffer()
207 ct.show(ctx, root=self.repo.root, file=path)
206 ct.show(ctx, root=self.repo.root, file=path)
208 ekw = templatefilters.firstline(self.ui.popbuffer())
207 ekw = templatefilters.firstline(self.ui.popbuffer())
209 return '$%s: %s $' % (kw, ekw)
208 return '$%s: %s $' % (kw, ekw)
210 return subfunc(kwsub, data)
209 return subfunc(kwsub, data)
211
210
212 def linkctx(self, path, fileid):
211 def linkctx(self, path, fileid):
213 '''Similar to filelog.linkrev, but returns a changectx.'''
212 '''Similar to filelog.linkrev, but returns a changectx.'''
214 return self.repo.filectx(path, fileid=fileid).changectx()
213 return self.repo.filectx(path, fileid=fileid).changectx()
215
214
216 def expand(self, path, node, data):
215 def expand(self, path, node, data):
217 '''Returns data with keywords expanded.'''
216 '''Returns data with keywords expanded.'''
218 if not self.restrict and self.match(path) and not util.binary(data):
217 if not self.restrict and self.match(path) and not util.binary(data):
219 ctx = self.linkctx(path, node)
218 ctx = self.linkctx(path, node)
220 return self.substitute(data, path, ctx, self.rekw.sub)
219 return self.substitute(data, path, ctx, self.rekw.sub)
221 return data
220 return data
222
221
223 def iskwfile(self, cand, ctx):
222 def iskwfile(self, cand, ctx):
224 '''Returns subset of candidates which are configured for keyword
223 '''Returns subset of candidates which are configured for keyword
225 expansion are not symbolic links.'''
224 expansion are not symbolic links.'''
226 return [f for f in cand if self.match(f) and not 'l' in ctx.flags(f)]
225 return [f for f in cand if self.match(f) and not 'l' in ctx.flags(f)]
227
226
228 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
227 def overwrite(self, ctx, candidates, lookup, expand, rekw=False):
229 '''Overwrites selected files expanding/shrinking keywords.'''
228 '''Overwrites selected files expanding/shrinking keywords.'''
230 if self.restrict or lookup or self.record: # exclude kw_copy
229 if self.restrict or lookup or self.record: # exclude kw_copy
231 candidates = self.iskwfile(candidates, ctx)
230 candidates = self.iskwfile(candidates, ctx)
232 if not candidates:
231 if not candidates:
233 return
232 return
234 kwcmd = self.restrict and lookup # kwexpand/kwshrink
233 kwcmd = self.restrict and lookup # kwexpand/kwshrink
235 if self.restrict or expand and lookup:
234 if self.restrict or expand and lookup:
236 mf = ctx.manifest()
235 mf = ctx.manifest()
237 lctx = ctx
236 lctx = ctx
238 re_kw = (self.restrict or rekw) and self.rekw or self.rekwexp
237 re_kw = (self.restrict or rekw) and self.rekw or self.rekwexp
239 msg = (expand and _('overwriting %s expanding keywords\n')
238 msg = (expand and _('overwriting %s expanding keywords\n')
240 or _('overwriting %s shrinking keywords\n'))
239 or _('overwriting %s shrinking keywords\n'))
241 for f in candidates:
240 for f in candidates:
242 if self.restrict:
241 if self.restrict:
243 data = self.repo.file(f).read(mf[f])
242 data = self.repo.file(f).read(mf[f])
244 else:
243 else:
245 data = self.repo.wread(f)
244 data = self.repo.wread(f)
246 if util.binary(data):
245 if util.binary(data):
247 continue
246 continue
248 if expand:
247 if expand:
249 if lookup:
248 if lookup:
250 lctx = self.linkctx(f, mf[f])
249 lctx = self.linkctx(f, mf[f])
251 data, found = self.substitute(data, f, lctx, re_kw.subn)
250 data, found = self.substitute(data, f, lctx, re_kw.subn)
252 elif self.restrict:
251 elif self.restrict:
253 found = re_kw.search(data)
252 found = re_kw.search(data)
254 else:
253 else:
255 data, found = _shrinktext(data, re_kw.subn)
254 data, found = _shrinktext(data, re_kw.subn)
256 if found:
255 if found:
257 self.ui.note(msg % f)
256 self.ui.note(msg % f)
258 self.repo.wwrite(f, data, ctx.flags(f))
257 self.repo.wwrite(f, data, ctx.flags(f))
259 if kwcmd:
258 if kwcmd:
260 self.repo.dirstate.normal(f)
259 self.repo.dirstate.normal(f)
261 elif self.record:
260 elif self.record:
262 self.repo.dirstate.normallookup(f)
261 self.repo.dirstate.normallookup(f)
263
262
264 def shrink(self, fname, text):
263 def shrink(self, fname, text):
265 '''Returns text with all keyword substitutions removed.'''
264 '''Returns text with all keyword substitutions removed.'''
266 if self.match(fname) and not util.binary(text):
265 if self.match(fname) and not util.binary(text):
267 return _shrinktext(text, self.rekwexp.sub)
266 return _shrinktext(text, self.rekwexp.sub)
268 return text
267 return text
269
268
270 def shrinklines(self, fname, lines):
269 def shrinklines(self, fname, lines):
271 '''Returns lines with keyword substitutions removed.'''
270 '''Returns lines with keyword substitutions removed.'''
272 if self.match(fname):
271 if self.match(fname):
273 text = ''.join(lines)
272 text = ''.join(lines)
274 if not util.binary(text):
273 if not util.binary(text):
275 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
274 return _shrinktext(text, self.rekwexp.sub).splitlines(True)
276 return lines
275 return lines
277
276
278 def wread(self, fname, data):
277 def wread(self, fname, data):
279 '''If in restricted mode returns data read from wdir with
278 '''If in restricted mode returns data read from wdir with
280 keyword substitutions removed.'''
279 keyword substitutions removed.'''
281 return self.restrict and self.shrink(fname, data) or data
280 return self.restrict and self.shrink(fname, data) or data
282
281
283 class kwfilelog(filelog.filelog):
282 class kwfilelog(filelog.filelog):
284 '''
283 '''
285 Subclass of filelog to hook into its read, add, cmp methods.
284 Subclass of filelog to hook into its read, add, cmp methods.
286 Keywords are "stored" unexpanded, and processed on reading.
285 Keywords are "stored" unexpanded, and processed on reading.
287 '''
286 '''
288 def __init__(self, opener, kwt, path):
287 def __init__(self, opener, kwt, path):
289 super(kwfilelog, self).__init__(opener, path)
288 super(kwfilelog, self).__init__(opener, path)
290 self.kwt = kwt
289 self.kwt = kwt
291 self.path = path
290 self.path = path
292
291
293 def read(self, node):
292 def read(self, node):
294 '''Expands keywords when reading filelog.'''
293 '''Expands keywords when reading filelog.'''
295 data = super(kwfilelog, self).read(node)
294 data = super(kwfilelog, self).read(node)
296 if self.renamed(node):
295 if self.renamed(node):
297 return data
296 return data
298 return self.kwt.expand(self.path, node, data)
297 return self.kwt.expand(self.path, node, data)
299
298
300 def add(self, text, meta, tr, link, p1=None, p2=None):
299 def add(self, text, meta, tr, link, p1=None, p2=None):
301 '''Removes keyword substitutions when adding to filelog.'''
300 '''Removes keyword substitutions when adding to filelog.'''
302 text = self.kwt.shrink(self.path, text)
301 text = self.kwt.shrink(self.path, text)
303 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
302 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
304
303
305 def cmp(self, node, text):
304 def cmp(self, node, text):
306 '''Removes keyword substitutions for comparison.'''
305 '''Removes keyword substitutions for comparison.'''
307 text = self.kwt.shrink(self.path, text)
306 text = self.kwt.shrink(self.path, text)
308 return super(kwfilelog, self).cmp(node, text)
307 return super(kwfilelog, self).cmp(node, text)
309
308
310 def _status(ui, repo, kwt, *pats, **opts):
309 def _status(ui, repo, kwt, *pats, **opts):
311 '''Bails out if [keyword] configuration is not active.
310 '''Bails out if [keyword] configuration is not active.
312 Returns status of working directory.'''
311 Returns status of working directory.'''
313 if kwt:
312 if kwt:
314 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
313 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
315 unknown=opts.get('unknown') or opts.get('all'))
314 unknown=opts.get('unknown') or opts.get('all'))
316 if ui.configitems('keyword'):
315 if ui.configitems('keyword'):
317 raise util.Abort(_('[keyword] patterns cannot match'))
316 raise util.Abort(_('[keyword] patterns cannot match'))
318 raise util.Abort(_('no [keyword] patterns configured'))
317 raise util.Abort(_('no [keyword] patterns configured'))
319
318
320 def _kwfwrite(ui, repo, expand, *pats, **opts):
319 def _kwfwrite(ui, repo, expand, *pats, **opts):
321 '''Selects files and passes them to kwtemplater.overwrite.'''
320 '''Selects files and passes them to kwtemplater.overwrite.'''
322 wctx = repo[None]
321 wctx = repo[None]
323 if len(wctx.parents()) > 1:
322 if len(wctx.parents()) > 1:
324 raise util.Abort(_('outstanding uncommitted merge'))
323 raise util.Abort(_('outstanding uncommitted merge'))
325 kwt = kwtools['templater']
324 kwt = kwtools['templater']
326 wlock = repo.wlock()
325 wlock = repo.wlock()
327 try:
326 try:
328 status = _status(ui, repo, kwt, *pats, **opts)
327 status = _status(ui, repo, kwt, *pats, **opts)
329 modified, added, removed, deleted, unknown, ignored, clean = status
328 modified, added, removed, deleted, unknown, ignored, clean = status
330 if modified or added or removed or deleted:
329 if modified or added or removed or deleted:
331 raise util.Abort(_('outstanding uncommitted changes'))
330 raise util.Abort(_('outstanding uncommitted changes'))
332 kwt.overwrite(wctx, clean, True, expand)
331 kwt.overwrite(wctx, clean, True, expand)
333 finally:
332 finally:
334 wlock.release()
333 wlock.release()
335
334
336 def demo(ui, repo, *args, **opts):
335 def demo(ui, repo, *args, **opts):
337 '''print [keywordmaps] configuration and an expansion example
336 '''print [keywordmaps] configuration and an expansion example
338
337
339 Show current, custom, or default keyword template maps and their
338 Show current, custom, or default keyword template maps and their
340 expansions.
339 expansions.
341
340
342 Extend the current configuration by specifying maps as arguments
341 Extend the current configuration by specifying maps as arguments
343 and using -f/--rcfile to source an external hgrc file.
342 and using -f/--rcfile to source an external hgrc file.
344
343
345 Use -d/--default to disable current configuration.
344 Use -d/--default to disable current configuration.
346
345
347 See :hg:`help templates` for information on templates and filters.
346 See :hg:`help templates` for information on templates and filters.
348 '''
347 '''
349 def demoitems(section, items):
348 def demoitems(section, items):
350 ui.write('[%s]\n' % section)
349 ui.write('[%s]\n' % section)
351 for k, v in sorted(items):
350 for k, v in sorted(items):
352 ui.write('%s = %s\n' % (k, v))
351 ui.write('%s = %s\n' % (k, v))
353
352
354 fn = 'demo.txt'
353 fn = 'demo.txt'
355 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
354 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
356 ui.note(_('creating temporary repository at %s\n') % tmpdir)
355 ui.note(_('creating temporary repository at %s\n') % tmpdir)
357 repo = localrepo.localrepository(ui, tmpdir, True)
356 repo = localrepo.localrepository(ui, tmpdir, True)
358 ui.setconfig('keyword', fn, '')
357 ui.setconfig('keyword', fn, '')
359
358
360 uikwmaps = ui.configitems('keywordmaps')
359 uikwmaps = ui.configitems('keywordmaps')
361 if args or opts.get('rcfile'):
360 if args or opts.get('rcfile'):
362 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
361 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
363 if uikwmaps:
362 if uikwmaps:
364 ui.status(_('\textending current template maps\n'))
363 ui.status(_('\textending current template maps\n'))
365 if opts.get('default') or not uikwmaps:
364 if opts.get('default') or not uikwmaps:
366 ui.status(_('\toverriding default template maps\n'))
365 ui.status(_('\toverriding default template maps\n'))
367 if opts.get('rcfile'):
366 if opts.get('rcfile'):
368 ui.readconfig(opts.get('rcfile'))
367 ui.readconfig(opts.get('rcfile'))
369 if args:
368 if args:
370 # simulate hgrc parsing
369 # simulate hgrc parsing
371 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
370 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
372 fp = repo.opener('hgrc', 'w')
371 fp = repo.opener('hgrc', 'w')
373 fp.writelines(rcmaps)
372 fp.writelines(rcmaps)
374 fp.close()
373 fp.close()
375 ui.readconfig(repo.join('hgrc'))
374 ui.readconfig(repo.join('hgrc'))
376 kwmaps = dict(ui.configitems('keywordmaps'))
375 kwmaps = dict(ui.configitems('keywordmaps'))
377 elif opts.get('default'):
376 elif opts.get('default'):
378 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
377 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
379 kwmaps = _defaultkwmaps(ui)
378 kwmaps = _defaultkwmaps(ui)
380 if uikwmaps:
379 if uikwmaps:
381 ui.status(_('\tdisabling current template maps\n'))
380 ui.status(_('\tdisabling current template maps\n'))
382 for k, v in kwmaps.iteritems():
381 for k, v in kwmaps.iteritems():
383 ui.setconfig('keywordmaps', k, v)
382 ui.setconfig('keywordmaps', k, v)
384 else:
383 else:
385 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
384 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
386 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
385 kwmaps = dict(uikwmaps) or _defaultkwmaps(ui)
387
386
388 uisetup(ui)
387 uisetup(ui)
389 reposetup(ui, repo)
388 reposetup(ui, repo)
390 ui.write('[extensions]\nkeyword =\n')
389 ui.write('[extensions]\nkeyword =\n')
391 demoitems('keyword', ui.configitems('keyword'))
390 demoitems('keyword', ui.configitems('keyword'))
392 demoitems('keywordmaps', kwmaps.iteritems())
391 demoitems('keywordmaps', kwmaps.iteritems())
393 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
392 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
394 repo.wopener(fn, 'w').write(keywords)
393 repo.wopener(fn, 'w').write(keywords)
395 repo[None].add([fn])
394 repo[None].add([fn])
396 ui.note(_('\nkeywords written to %s:\n') % fn)
395 ui.note(_('\nkeywords written to %s:\n') % fn)
397 ui.note(keywords)
396 ui.note(keywords)
398 repo.dirstate.setbranch('demobranch')
397 repo.dirstate.setbranch('demobranch')
399 for name, cmd in ui.configitems('hooks'):
398 for name, cmd in ui.configitems('hooks'):
400 if name.split('.', 1)[0].find('commit') > -1:
399 if name.split('.', 1)[0].find('commit') > -1:
401 repo.ui.setconfig('hooks', name, '')
400 repo.ui.setconfig('hooks', name, '')
402 msg = _('hg keyword configuration and expansion example')
401 msg = _('hg keyword configuration and expansion example')
403 ui.note("hg ci -m '%s'\n" % msg)
402 ui.note("hg ci -m '%s'\n" % msg)
404 repo.commit(text=msg)
403 repo.commit(text=msg)
405 ui.status(_('\n\tkeywords expanded\n'))
404 ui.status(_('\n\tkeywords expanded\n'))
406 ui.write(repo.wread(fn))
405 ui.write(repo.wread(fn))
407 shutil.rmtree(tmpdir, ignore_errors=True)
406 shutil.rmtree(tmpdir, ignore_errors=True)
408
407
409 def expand(ui, repo, *pats, **opts):
408 def expand(ui, repo, *pats, **opts):
410 '''expand keywords in the working directory
409 '''expand keywords in the working directory
411
410
412 Run after (re)enabling keyword expansion.
411 Run after (re)enabling keyword expansion.
413
412
414 kwexpand refuses to run if given files contain local changes.
413 kwexpand refuses to run if given files contain local changes.
415 '''
414 '''
416 # 3rd argument sets expansion to True
415 # 3rd argument sets expansion to True
417 _kwfwrite(ui, repo, True, *pats, **opts)
416 _kwfwrite(ui, repo, True, *pats, **opts)
418
417
419 def files(ui, repo, *pats, **opts):
418 def files(ui, repo, *pats, **opts):
420 '''show files configured for keyword expansion
419 '''show files configured for keyword expansion
421
420
422 List which files in the working directory are matched by the
421 List which files in the working directory are matched by the
423 [keyword] configuration patterns.
422 [keyword] configuration patterns.
424
423
425 Useful to prevent inadvertent keyword expansion and to speed up
424 Useful to prevent inadvertent keyword expansion and to speed up
426 execution by including only files that are actual candidates for
425 execution by including only files that are actual candidates for
427 expansion.
426 expansion.
428
427
429 See :hg:`help keyword` on how to construct patterns both for
428 See :hg:`help keyword` on how to construct patterns both for
430 inclusion and exclusion of files.
429 inclusion and exclusion of files.
431
430
432 With -A/--all and -v/--verbose the codes used to show the status
431 With -A/--all and -v/--verbose the codes used to show the status
433 of files are::
432 of files are::
434
433
435 K = keyword expansion candidate
434 K = keyword expansion candidate
436 k = keyword expansion candidate (not tracked)
435 k = keyword expansion candidate (not tracked)
437 I = ignored
436 I = ignored
438 i = ignored (not tracked)
437 i = ignored (not tracked)
439 '''
438 '''
440 kwt = kwtools['templater']
439 kwt = kwtools['templater']
441 status = _status(ui, repo, kwt, *pats, **opts)
440 status = _status(ui, repo, kwt, *pats, **opts)
442 cwd = pats and repo.getcwd() or ''
441 cwd = pats and repo.getcwd() or ''
443 modified, added, removed, deleted, unknown, ignored, clean = status
442 modified, added, removed, deleted, unknown, ignored, clean = status
444 files = []
443 files = []
445 if not opts.get('unknown') or opts.get('all'):
444 if not opts.get('unknown') or opts.get('all'):
446 files = sorted(modified + added + clean)
445 files = sorted(modified + added + clean)
447 wctx = repo[None]
446 wctx = repo[None]
448 kwfiles = kwt.iskwfile(files, wctx)
447 kwfiles = kwt.iskwfile(files, wctx)
449 kwdeleted = kwt.iskwfile(deleted, wctx)
448 kwdeleted = kwt.iskwfile(deleted, wctx)
450 kwunknown = kwt.iskwfile(unknown, wctx)
449 kwunknown = kwt.iskwfile(unknown, wctx)
451 if not opts.get('ignore') or opts.get('all'):
450 if not opts.get('ignore') or opts.get('all'):
452 showfiles = kwfiles, kwdeleted, kwunknown
451 showfiles = kwfiles, kwdeleted, kwunknown
453 else:
452 else:
454 showfiles = [], [], []
453 showfiles = [], [], []
455 if opts.get('all') or opts.get('ignore'):
454 if opts.get('all') or opts.get('ignore'):
456 showfiles += ([f for f in files if f not in kwfiles],
455 showfiles += ([f for f in files if f not in kwfiles],
457 [f for f in unknown if f not in kwunknown])
456 [f for f in unknown if f not in kwunknown])
458 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
457 kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
459 kwstates = zip('K!kIi', showfiles, kwlabels)
458 kwstates = zip('K!kIi', showfiles, kwlabels)
460 for char, filenames, kwstate in kwstates:
459 for char, filenames, kwstate in kwstates:
461 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
460 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
462 for f in filenames:
461 for f in filenames:
463 ui.write(fmt % repo.pathto(f, cwd), label='kwfiles.' + kwstate)
462 ui.write(fmt % repo.pathto(f, cwd), label='kwfiles.' + kwstate)
464
463
465 def shrink(ui, repo, *pats, **opts):
464 def shrink(ui, repo, *pats, **opts):
466 '''revert expanded keywords in the working directory
465 '''revert expanded keywords in the working directory
467
466
468 Run before changing/disabling active keywords or if you experience
467 Must be run before changing/disabling active keywords.
469 problems with :hg:`import` or :hg:`merge`.
470
468
471 kwshrink refuses to run if given files contain local changes.
469 kwshrink refuses to run if given files contain local changes.
472 '''
470 '''
473 # 3rd argument sets expansion to False
471 # 3rd argument sets expansion to False
474 _kwfwrite(ui, repo, False, *pats, **opts)
472 _kwfwrite(ui, repo, False, *pats, **opts)
475
473
476
474
477 def uisetup(ui):
475 def uisetup(ui):
478 ''' Monkeypatches dispatch._parse to retrieve user command.'''
476 ''' Monkeypatches dispatch._parse to retrieve user command.'''
479
477
480 def kwdispatch_parse(orig, ui, args):
478 def kwdispatch_parse(orig, ui, args):
481 '''Monkeypatch dispatch._parse to obtain running hg command.'''
479 '''Monkeypatch dispatch._parse to obtain running hg command.'''
482 cmd, func, args, options, cmdoptions = orig(ui, args)
480 cmd, func, args, options, cmdoptions = orig(ui, args)
483 kwtools['hgcmd'] = cmd
481 kwtools['hgcmd'] = cmd
484 return cmd, func, args, options, cmdoptions
482 return cmd, func, args, options, cmdoptions
485
483
486 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
484 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
487
485
488 def reposetup(ui, repo):
486 def reposetup(ui, repo):
489 '''Sets up repo as kwrepo for keyword substitution.
487 '''Sets up repo as kwrepo for keyword substitution.
490 Overrides file method to return kwfilelog instead of filelog
488 Overrides file method to return kwfilelog instead of filelog
491 if file matches user configuration.
489 if file matches user configuration.
492 Wraps commit to overwrite configured files with updated
490 Wraps commit to overwrite configured files with updated
493 keyword substitutions.
491 keyword substitutions.
494 Monkeypatches patch and webcommands.'''
492 Monkeypatches patch and webcommands.'''
495
493
496 try:
494 try:
497 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
495 if (not repo.local() or kwtools['hgcmd'] in nokwcommands.split()
498 or '.hg' in util.splitpath(repo.root)
496 or '.hg' in util.splitpath(repo.root)
499 or repo._url.startswith('bundle:')):
497 or repo._url.startswith('bundle:')):
500 return
498 return
501 except AttributeError:
499 except AttributeError:
502 pass
500 pass
503
501
504 inc, exc = [], ['.hg*']
502 inc, exc = [], ['.hg*']
505 for pat, opt in ui.configitems('keyword'):
503 for pat, opt in ui.configitems('keyword'):
506 if opt != 'ignore':
504 if opt != 'ignore':
507 inc.append(pat)
505 inc.append(pat)
508 else:
506 else:
509 exc.append(pat)
507 exc.append(pat)
510 if not inc:
508 if not inc:
511 return
509 return
512
510
513 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
511 kwtools['templater'] = kwt = kwtemplater(ui, repo, inc, exc)
514
512
515 class kwrepo(repo.__class__):
513 class kwrepo(repo.__class__):
516 def file(self, f):
514 def file(self, f):
517 if f[0] == '/':
515 if f[0] == '/':
518 f = f[1:]
516 f = f[1:]
519 return kwfilelog(self.sopener, kwt, f)
517 return kwfilelog(self.sopener, kwt, f)
520
518
521 def wread(self, filename):
519 def wread(self, filename):
522 data = super(kwrepo, self).wread(filename)
520 data = super(kwrepo, self).wread(filename)
523 return kwt.wread(filename, data)
521 return kwt.wread(filename, data)
524
522
525 def commit(self, *args, **opts):
523 def commit(self, *args, **opts):
526 # use custom commitctx for user commands
524 # use custom commitctx for user commands
527 # other extensions can still wrap repo.commitctx directly
525 # other extensions can still wrap repo.commitctx directly
528 self.commitctx = self.kwcommitctx
526 self.commitctx = self.kwcommitctx
529 try:
527 try:
530 return super(kwrepo, self).commit(*args, **opts)
528 return super(kwrepo, self).commit(*args, **opts)
531 finally:
529 finally:
532 del self.commitctx
530 del self.commitctx
533
531
534 def kwcommitctx(self, ctx, error=False):
532 def kwcommitctx(self, ctx, error=False):
535 n = super(kwrepo, self).commitctx(ctx, error)
533 n = super(kwrepo, self).commitctx(ctx, error)
536 # no lock needed, only called from repo.commit() which already locks
534 # no lock needed, only called from repo.commit() which already locks
537 if not kwt.record:
535 if not kwt.record:
538 restrict = kwt.restrict
536 restrict = kwt.restrict
539 kwt.restrict = True
537 kwt.restrict = True
540 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
538 kwt.overwrite(self[n], sorted(ctx.added() + ctx.modified()),
541 False, True)
539 False, True)
542 kwt.restrict = restrict
540 kwt.restrict = restrict
543 return n
541 return n
544
542
545 def rollback(self, dryrun=False):
543 def rollback(self, dryrun=False):
546 wlock = self.wlock()
544 wlock = self.wlock()
547 try:
545 try:
548 if not dryrun:
546 if not dryrun:
549 changed = self['.'].files()
547 changed = self['.'].files()
550 ret = super(kwrepo, self).rollback(dryrun)
548 ret = super(kwrepo, self).rollback(dryrun)
551 if not dryrun:
549 if not dryrun:
552 ctx = self['.']
550 ctx = self['.']
553 modified, added = _preselect(self[None].status(), changed)
551 modified, added = _preselect(self[None].status(), changed)
554 kwt.overwrite(ctx, modified, True, True)
552 kwt.overwrite(ctx, modified, True, True)
555 kwt.overwrite(ctx, added, True, False)
553 kwt.overwrite(ctx, added, True, False)
556 return ret
554 return ret
557 finally:
555 finally:
558 wlock.release()
556 wlock.release()
559
557
560 # monkeypatches
558 # monkeypatches
561 def kwpatchfile_init(orig, self, ui, fname, opener,
559 def kwpatchfile_init(orig, self, ui, fname, opener,
562 missing=False, eolmode=None):
560 missing=False, eolmode=None):
563 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
561 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
564 rejects or conflicts due to expanded keywords in working dir.'''
562 rejects or conflicts due to expanded keywords in working dir.'''
565 orig(self, ui, fname, opener, missing, eolmode)
563 orig(self, ui, fname, opener, missing, eolmode)
566 # shrink keywords read from working dir
564 # shrink keywords read from working dir
567 self.lines = kwt.shrinklines(self.fname, self.lines)
565 self.lines = kwt.shrinklines(self.fname, self.lines)
568
566
569 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
567 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
570 opts=None, prefix=''):
568 opts=None, prefix=''):
571 '''Monkeypatch patch.diff to avoid expansion.'''
569 '''Monkeypatch patch.diff to avoid expansion.'''
572 kwt.restrict = True
570 kwt.restrict = True
573 return orig(repo, node1, node2, match, changes, opts, prefix)
571 return orig(repo, node1, node2, match, changes, opts, prefix)
574
572
575 def kwweb_skip(orig, web, req, tmpl):
573 def kwweb_skip(orig, web, req, tmpl):
576 '''Wraps webcommands.x turning off keyword expansion.'''
574 '''Wraps webcommands.x turning off keyword expansion.'''
577 kwt.match = util.never
575 kwt.match = util.never
578 return orig(web, req, tmpl)
576 return orig(web, req, tmpl)
579
577
580 def kw_copy(orig, ui, repo, pats, opts, rename=False):
578 def kw_copy(orig, ui, repo, pats, opts, rename=False):
581 '''Wraps cmdutil.copy so that copy/rename destinations do not
579 '''Wraps cmdutil.copy so that copy/rename destinations do not
582 contain expanded keywords.
580 contain expanded keywords.
583 Note that the source of a regular file destination may also be a
581 Note that the source of a regular file destination may also be a
584 symlink:
582 symlink:
585 hg cp sym x -> x is symlink
583 hg cp sym x -> x is symlink
586 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
584 cp sym x; hg cp -A sym x -> x is file (maybe expanded keywords)
587 For the latter we have to follow the symlink to find out whether its
585 For the latter we have to follow the symlink to find out whether its
588 target is configured for expansion and we therefore must unexpand the
586 target is configured for expansion and we therefore must unexpand the
589 keywords in the destination.'''
587 keywords in the destination.'''
590 orig(ui, repo, pats, opts, rename)
588 orig(ui, repo, pats, opts, rename)
591 if opts.get('dry_run'):
589 if opts.get('dry_run'):
592 return
590 return
593 wctx = repo[None]
591 wctx = repo[None]
594 cwd = repo.getcwd()
592 cwd = repo.getcwd()
595
593
596 def haskwsource(dest):
594 def haskwsource(dest):
597 '''Returns true if dest is a regular file and configured for
595 '''Returns true if dest is a regular file and configured for
598 expansion or a symlink which points to a file configured for
596 expansion or a symlink which points to a file configured for
599 expansion. '''
597 expansion. '''
600 source = repo.dirstate.copied(dest)
598 source = repo.dirstate.copied(dest)
601 if 'l' in wctx.flags(source):
599 if 'l' in wctx.flags(source):
602 source = util.canonpath(repo.root, cwd,
600 source = util.canonpath(repo.root, cwd,
603 os.path.realpath(source))
601 os.path.realpath(source))
604 return kwt.match(source)
602 return kwt.match(source)
605
603
606 candidates = [f for f in repo.dirstate.copies() if
604 candidates = [f for f in repo.dirstate.copies() if
607 not 'l' in wctx.flags(f) and haskwsource(f)]
605 not 'l' in wctx.flags(f) and haskwsource(f)]
608 kwt.overwrite(wctx, candidates, False, False)
606 kwt.overwrite(wctx, candidates, False, False)
609
607
610 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
608 def kw_dorecord(orig, ui, repo, commitfunc, *pats, **opts):
611 '''Wraps record.dorecord expanding keywords after recording.'''
609 '''Wraps record.dorecord expanding keywords after recording.'''
612 wlock = repo.wlock()
610 wlock = repo.wlock()
613 try:
611 try:
614 # record returns 0 even when nothing has changed
612 # record returns 0 even when nothing has changed
615 # therefore compare nodes before and after
613 # therefore compare nodes before and after
616 kwt.record = True
614 kwt.record = True
617 ctx = repo['.']
615 ctx = repo['.']
618 wstatus = repo[None].status()
616 wstatus = repo[None].status()
619 ret = orig(ui, repo, commitfunc, *pats, **opts)
617 ret = orig(ui, repo, commitfunc, *pats, **opts)
620 recctx = repo['.']
618 recctx = repo['.']
621 if ctx != recctx:
619 if ctx != recctx:
622 modified, added = _preselect(wstatus, recctx.files())
620 modified, added = _preselect(wstatus, recctx.files())
623 kwt.restrict = False
621 kwt.restrict = False
624 kwt.overwrite(recctx, modified, False, True)
622 kwt.overwrite(recctx, modified, False, True)
625 kwt.overwrite(recctx, added, False, True, True)
623 kwt.overwrite(recctx, added, False, True, True)
626 kwt.restrict = True
624 kwt.restrict = True
627 return ret
625 return ret
628 finally:
626 finally:
629 wlock.release()
627 wlock.release()
630
628
631 repo.__class__ = kwrepo
629 repo.__class__ = kwrepo
632
630
633 def kwfilectx_cmp(orig, self, fctx):
631 def kwfilectx_cmp(orig, self, fctx):
634 # keyword affects data size, comparing wdir and filelog size does
632 # keyword affects data size, comparing wdir and filelog size does
635 # not make sense
633 # not make sense
636 if (fctx._filerev is None and
634 if (fctx._filerev is None and
637 (self._repo._encodefilterpats or
635 (self._repo._encodefilterpats or
638 kwt.match(fctx.path()) and not 'l' in fctx.flags()) or
636 kwt.match(fctx.path()) and not 'l' in fctx.flags()) or
639 self.size() == fctx.size()):
637 self.size() == fctx.size()):
640 return self._filelog.cmp(self._filenode, fctx.data())
638 return self._filelog.cmp(self._filenode, fctx.data())
641 return True
639 return True
642
640
643 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
641 extensions.wrapfunction(context.filectx, 'cmp', kwfilectx_cmp)
644 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
642 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
645 extensions.wrapfunction(patch, 'diff', kw_diff)
643 extensions.wrapfunction(patch, 'diff', kw_diff)
646 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
644 extensions.wrapfunction(cmdutil, 'copy', kw_copy)
647 for c in 'annotate changeset rev filediff diff'.split():
645 for c in 'annotate changeset rev filediff diff'.split():
648 extensions.wrapfunction(webcommands, c, kwweb_skip)
646 extensions.wrapfunction(webcommands, c, kwweb_skip)
649 for name in recordextensions.split():
647 for name in recordextensions.split():
650 try:
648 try:
651 record = extensions.find(name)
649 record = extensions.find(name)
652 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
650 extensions.wrapfunction(record, 'dorecord', kw_dorecord)
653 except KeyError:
651 except KeyError:
654 pass
652 pass
655
653
656 cmdtable = {
654 cmdtable = {
657 'kwdemo':
655 'kwdemo':
658 (demo,
656 (demo,
659 [('d', 'default', None, _('show default keyword template maps')),
657 [('d', 'default', None, _('show default keyword template maps')),
660 ('f', 'rcfile', '',
658 ('f', 'rcfile', '',
661 _('read maps from rcfile'), _('FILE'))],
659 _('read maps from rcfile'), _('FILE'))],
662 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
660 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
663 'kwexpand': (expand, commands.walkopts,
661 'kwexpand': (expand, commands.walkopts,
664 _('hg kwexpand [OPTION]... [FILE]...')),
662 _('hg kwexpand [OPTION]... [FILE]...')),
665 'kwfiles':
663 'kwfiles':
666 (files,
664 (files,
667 [('A', 'all', None, _('show keyword status flags of all files')),
665 [('A', 'all', None, _('show keyword status flags of all files')),
668 ('i', 'ignore', None, _('show files excluded from expansion')),
666 ('i', 'ignore', None, _('show files excluded from expansion')),
669 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
667 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
670 ] + commands.walkopts,
668 ] + commands.walkopts,
671 _('hg kwfiles [OPTION]... [FILE]...')),
669 _('hg kwfiles [OPTION]... [FILE]...')),
672 'kwshrink': (shrink, commands.walkopts,
670 'kwshrink': (shrink, commands.walkopts,
673 _('hg kwshrink [OPTION]... [FILE]...')),
671 _('hg kwshrink [OPTION]... [FILE]...')),
674 }
672 }
General Comments 0
You need to be logged in to leave comments. Login now