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