##// END OF EJS Templates
keyword: remove deprecated options
Christian Ebert -
r10652:e7f840e4 default
parent child Browse files
Show More
@@ -1,542 +1,537 b''
1 # keyword.py - $Keyword$ expansion for Mercurial
1 # keyword.py - $Keyword$ expansion for Mercurial
2 #
2 #
3 # Copyright 2007-2009 Christian Ebert <blacktrash@gmx.net>
3 # Copyright 2007-2009 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 Configuration is done in the [keyword] and [keywordmaps] sections of
38 Configuration is done in the [keyword] and [keywordmaps] sections of
39 hgrc files.
39 hgrc files.
40
40
41 Example::
41 Example::
42
42
43 [keyword]
43 [keyword]
44 # expand keywords in every python file except those matching "x*"
44 # expand keywords in every python file except those matching "x*"
45 **.py =
45 **.py =
46 x* = ignore
46 x* = ignore
47
47
48 NOTE: the more specific you are in your filename patterns the less you
48 NOTE: the more specific you are in your filename patterns the less you
49 lose speed in huge repositories.
49 lose speed in huge repositories.
50
50
51 For [keywordmaps] template mapping and expansion demonstration and
51 For [keywordmaps] template mapping and expansion demonstration and
52 control run "hg kwdemo". See "hg help templates" for a list of
52 control run "hg kwdemo". See "hg help templates" for a list of
53 available templates and filters.
53 available templates and filters.
54
54
55 An additional date template filter {date|utcdate} is provided. It
55 An additional date template filter {date|utcdate} is provided. It
56 returns a date like "2006/09/18 15:13:13".
56 returns a date like "2006/09/18 15:13:13".
57
57
58 The default template mappings (view with "hg kwdemo -d") can be
58 The default template mappings (view with "hg kwdemo -d") can be
59 replaced with customized keywords and templates. Again, run "hg
59 replaced with customized keywords and templates. Again, run "hg
60 kwdemo" to control the results of your config changes.
60 kwdemo" to control the results of your config changes.
61
61
62 Before changing/disabling active keywords, run "hg kwshrink" to avoid
62 Before changing/disabling active keywords, run "hg kwshrink" to avoid
63 the risk of inadvertently storing expanded keywords in the change
63 the risk of inadvertently storing expanded keywords in the change
64 history.
64 history.
65
65
66 To force expansion after enabling it, or a configuration change, run
66 To force expansion after enabling it, or a configuration change, run
67 "hg kwexpand".
67 "hg kwexpand".
68
68
69 Also, when committing with the record extension or using mq's qrecord,
69 Also, when committing with the record extension or using mq's qrecord,
70 be aware that keywords cannot be updated. Again, run "hg kwexpand" on
70 be aware that keywords cannot be updated. Again, run "hg kwexpand" on
71 the files in question to update keyword expansions after all changes
71 the files in question to update keyword expansions after all changes
72 have been checked in.
72 have been checked in.
73
73
74 Expansions spanning more than one line and incremental expansions,
74 Expansions spanning more than one line and incremental expansions,
75 like CVS' $Log$, are not supported. A keyword template map "Log =
75 like CVS' $Log$, are not supported. A keyword template map "Log =
76 {desc}" expands to the first line of the changeset description.
76 {desc}" expands to the first line of the changeset description.
77 '''
77 '''
78
78
79 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
79 from mercurial import commands, cmdutil, dispatch, filelog, revlog, extensions
80 from mercurial import patch, localrepo, templater, templatefilters, util, match
80 from mercurial import patch, localrepo, templater, templatefilters, util, match
81 from mercurial.hgweb import webcommands
81 from mercurial.hgweb import webcommands
82 from mercurial.node import nullid
82 from mercurial.node import nullid
83 from mercurial.i18n import _
83 from mercurial.i18n import _
84 import re, shutil, tempfile
84 import re, shutil, tempfile
85
85
86 commands.optionalrepo += ' kwdemo'
86 commands.optionalrepo += ' kwdemo'
87
87
88 # hg commands that do not act on keywords
88 # hg commands that do not act on keywords
89 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
89 nokwcommands = ('add addremove annotate bundle copy export grep incoming init'
90 ' log outgoing push rename rollback tip verify'
90 ' log outgoing push rename rollback tip verify'
91 ' convert email glog')
91 ' convert email glog')
92
92
93 # hg commands that trigger expansion only when writing to working dir,
93 # hg commands that trigger expansion only when writing to working dir,
94 # not when reading filelog, and unexpand when reading from working dir
94 # not when reading filelog, and unexpand when reading from working dir
95 restricted = ('merge record resolve qfold qimport qnew qpush qrefresh qrecord'
95 restricted = ('merge record resolve qfold qimport qnew qpush qrefresh qrecord'
96 ' transplant')
96 ' transplant')
97
97
98 # provide cvs-like UTC date filter
98 # provide cvs-like UTC date filter
99 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
99 utcdate = lambda x: util.datestr((x[0], 0), '%Y/%m/%d %H:%M:%S')
100
100
101 # make keyword tools accessible
101 # make keyword tools accessible
102 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
102 kwtools = {'templater': None, 'hgcmd': '', 'inc': [], 'exc': ['.hg*']}
103
103
104
104
105 class kwtemplater(object):
105 class kwtemplater(object):
106 '''
106 '''
107 Sets up keyword templates, corresponding keyword regex, and
107 Sets up keyword templates, corresponding keyword regex, and
108 provides keyword substitution functions.
108 provides keyword substitution functions.
109 '''
109 '''
110 templates = {
110 templates = {
111 'Revision': '{node|short}',
111 'Revision': '{node|short}',
112 'Author': '{author|user}',
112 'Author': '{author|user}',
113 'Date': '{date|utcdate}',
113 'Date': '{date|utcdate}',
114 'RCSfile': '{file|basename},v',
114 'RCSfile': '{file|basename},v',
115 'RCSFile': '{file|basename},v', # kept for backwards compatibility
115 'RCSFile': '{file|basename},v', # kept for backwards compatibility
116 # with hg-keyword
116 # with hg-keyword
117 'Source': '{root}/{file},v',
117 'Source': '{root}/{file},v',
118 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
118 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
119 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
119 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
120 }
120 }
121
121
122 def __init__(self, ui, repo):
122 def __init__(self, ui, repo):
123 self.ui = ui
123 self.ui = ui
124 self.repo = repo
124 self.repo = repo
125 self.match = match.match(repo.root, '', [],
125 self.match = match.match(repo.root, '', [],
126 kwtools['inc'], kwtools['exc'])
126 kwtools['inc'], kwtools['exc'])
127 self.restrict = kwtools['hgcmd'] in restricted.split()
127 self.restrict = kwtools['hgcmd'] in restricted.split()
128
128
129 kwmaps = self.ui.configitems('keywordmaps')
129 kwmaps = self.ui.configitems('keywordmaps')
130 if kwmaps: # override default templates
130 if kwmaps: # override default templates
131 self.templates = dict((k, templater.parsestring(v, False))
131 self.templates = dict((k, templater.parsestring(v, False))
132 for k, v in kwmaps)
132 for k, v in kwmaps)
133 escaped = map(re.escape, self.templates.keys())
133 escaped = map(re.escape, self.templates.keys())
134 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
134 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
135 self.re_kw = re.compile(kwpat)
135 self.re_kw = re.compile(kwpat)
136
136
137 templatefilters.filters['utcdate'] = utcdate
137 templatefilters.filters['utcdate'] = utcdate
138 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
138 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
139 False, None, '', False)
139 False, None, '', False)
140
140
141 def substitute(self, data, path, ctx, subfunc):
141 def substitute(self, data, path, ctx, subfunc):
142 '''Replaces keywords in data with expanded template.'''
142 '''Replaces keywords in data with expanded template.'''
143 def kwsub(mobj):
143 def kwsub(mobj):
144 kw = mobj.group(1)
144 kw = mobj.group(1)
145 self.ct.use_template(self.templates[kw])
145 self.ct.use_template(self.templates[kw])
146 self.ui.pushbuffer()
146 self.ui.pushbuffer()
147 self.ct.show(ctx, root=self.repo.root, file=path)
147 self.ct.show(ctx, root=self.repo.root, file=path)
148 ekw = templatefilters.firstline(self.ui.popbuffer())
148 ekw = templatefilters.firstline(self.ui.popbuffer())
149 return '$%s: %s $' % (kw, ekw)
149 return '$%s: %s $' % (kw, ekw)
150 return subfunc(kwsub, data)
150 return subfunc(kwsub, data)
151
151
152 def expand(self, path, node, data):
152 def expand(self, path, node, data):
153 '''Returns data with keywords expanded.'''
153 '''Returns data with keywords expanded.'''
154 if not self.restrict and self.match(path) and not util.binary(data):
154 if not self.restrict and self.match(path) and not util.binary(data):
155 ctx = self.repo.filectx(path, fileid=node).changectx()
155 ctx = self.repo.filectx(path, fileid=node).changectx()
156 return self.substitute(data, path, ctx, self.re_kw.sub)
156 return self.substitute(data, path, ctx, self.re_kw.sub)
157 return data
157 return data
158
158
159 def iskwfile(self, path, flagfunc):
159 def iskwfile(self, path, flagfunc):
160 '''Returns true if path matches [keyword] pattern
160 '''Returns true if path matches [keyword] pattern
161 and is not a symbolic link.
161 and is not a symbolic link.
162 Caveat: localrepository._link fails on Windows.'''
162 Caveat: localrepository._link fails on Windows.'''
163 return self.match(path) and not 'l' in flagfunc(path)
163 return self.match(path) and not 'l' in flagfunc(path)
164
164
165 def overwrite(self, node, expand, files):
165 def overwrite(self, node, expand, files):
166 '''Overwrites selected files expanding/shrinking keywords.'''
166 '''Overwrites selected files expanding/shrinking keywords.'''
167 ctx = self.repo[node]
167 ctx = self.repo[node]
168 mf = ctx.manifest()
168 mf = ctx.manifest()
169 if node is not None: # commit
169 if node is not None: # commit
170 files = [f for f in ctx.files() if f in mf]
170 files = [f for f in ctx.files() if f in mf]
171 notify = self.ui.debug
171 notify = self.ui.debug
172 else: # kwexpand/kwshrink
172 else: # kwexpand/kwshrink
173 notify = self.ui.note
173 notify = self.ui.note
174 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
174 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
175 if candidates:
175 if candidates:
176 self.restrict = True # do not expand when reading
176 self.restrict = True # do not expand when reading
177 msg = (expand and _('overwriting %s expanding keywords\n')
177 msg = (expand and _('overwriting %s expanding keywords\n')
178 or _('overwriting %s shrinking keywords\n'))
178 or _('overwriting %s shrinking keywords\n'))
179 for f in candidates:
179 for f in candidates:
180 fp = self.repo.file(f)
180 fp = self.repo.file(f)
181 data = fp.read(mf[f])
181 data = fp.read(mf[f])
182 if util.binary(data):
182 if util.binary(data):
183 continue
183 continue
184 if expand:
184 if expand:
185 if node is None:
185 if node is None:
186 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
186 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
187 data, found = self.substitute(data, f, ctx,
187 data, found = self.substitute(data, f, ctx,
188 self.re_kw.subn)
188 self.re_kw.subn)
189 else:
189 else:
190 found = self.re_kw.search(data)
190 found = self.re_kw.search(data)
191 if found:
191 if found:
192 notify(msg % f)
192 notify(msg % f)
193 self.repo.wwrite(f, data, mf.flags(f))
193 self.repo.wwrite(f, data, mf.flags(f))
194 if node is None:
194 if node is None:
195 self.repo.dirstate.normal(f)
195 self.repo.dirstate.normal(f)
196 self.restrict = False
196 self.restrict = False
197
197
198 def shrinktext(self, text):
198 def shrinktext(self, text):
199 '''Unconditionally removes all keyword substitutions from text.'''
199 '''Unconditionally removes all keyword substitutions from text.'''
200 return self.re_kw.sub(r'$\1$', text)
200 return self.re_kw.sub(r'$\1$', text)
201
201
202 def shrink(self, fname, text):
202 def shrink(self, fname, text):
203 '''Returns text with all keyword substitutions removed.'''
203 '''Returns text with all keyword substitutions removed.'''
204 if self.match(fname) and not util.binary(text):
204 if self.match(fname) and not util.binary(text):
205 return self.shrinktext(text)
205 return self.shrinktext(text)
206 return text
206 return text
207
207
208 def shrinklines(self, fname, lines):
208 def shrinklines(self, fname, lines):
209 '''Returns lines with keyword substitutions removed.'''
209 '''Returns lines with keyword substitutions removed.'''
210 if self.match(fname):
210 if self.match(fname):
211 text = ''.join(lines)
211 text = ''.join(lines)
212 if not util.binary(text):
212 if not util.binary(text):
213 return self.shrinktext(text).splitlines(True)
213 return self.shrinktext(text).splitlines(True)
214 return lines
214 return lines
215
215
216 def wread(self, fname, data):
216 def wread(self, fname, data):
217 '''If in restricted mode returns data read from wdir with
217 '''If in restricted mode returns data read from wdir with
218 keyword substitutions removed.'''
218 keyword substitutions removed.'''
219 return self.restrict and self.shrink(fname, data) or data
219 return self.restrict and self.shrink(fname, data) or data
220
220
221 class kwfilelog(filelog.filelog):
221 class kwfilelog(filelog.filelog):
222 '''
222 '''
223 Subclass of filelog to hook into its read, add, cmp methods.
223 Subclass of filelog to hook into its read, add, cmp methods.
224 Keywords are "stored" unexpanded, and processed on reading.
224 Keywords are "stored" unexpanded, and processed on reading.
225 '''
225 '''
226 def __init__(self, opener, kwt, path):
226 def __init__(self, opener, kwt, path):
227 super(kwfilelog, self).__init__(opener, path)
227 super(kwfilelog, self).__init__(opener, path)
228 self.kwt = kwt
228 self.kwt = kwt
229 self.path = path
229 self.path = path
230
230
231 def read(self, node):
231 def read(self, node):
232 '''Expands keywords when reading filelog.'''
232 '''Expands keywords when reading filelog.'''
233 data = super(kwfilelog, self).read(node)
233 data = super(kwfilelog, self).read(node)
234 return self.kwt.expand(self.path, node, data)
234 return self.kwt.expand(self.path, node, data)
235
235
236 def add(self, text, meta, tr, link, p1=None, p2=None):
236 def add(self, text, meta, tr, link, p1=None, p2=None):
237 '''Removes keyword substitutions when adding to filelog.'''
237 '''Removes keyword substitutions when adding to filelog.'''
238 text = self.kwt.shrink(self.path, text)
238 text = self.kwt.shrink(self.path, text)
239 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
239 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
240
240
241 def cmp(self, node, text):
241 def cmp(self, node, text):
242 '''Removes keyword substitutions for comparison.'''
242 '''Removes keyword substitutions for comparison.'''
243 text = self.kwt.shrink(self.path, text)
243 text = self.kwt.shrink(self.path, text)
244 if self.renamed(node):
244 if self.renamed(node):
245 t2 = super(kwfilelog, self).read(node)
245 t2 = super(kwfilelog, self).read(node)
246 return t2 != text
246 return t2 != text
247 return revlog.revlog.cmp(self, node, text)
247 return revlog.revlog.cmp(self, node, text)
248
248
249 def _status(ui, repo, kwt, *pats, **opts):
249 def _status(ui, repo, kwt, *pats, **opts):
250 '''Bails out if [keyword] configuration is not active.
250 '''Bails out if [keyword] configuration is not active.
251 Returns status of working directory.'''
251 Returns status of working directory.'''
252 if kwt:
252 if kwt:
253 unknown = (opts.get('unknown') or opts.get('all')
254 or opts.get('untracked'))
255 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
253 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
256 unknown=unknown)
254 unknown=opts.get('unknown') or opts.get('all'))
257 if ui.configitems('keyword'):
255 if ui.configitems('keyword'):
258 raise util.Abort(_('[keyword] patterns cannot match'))
256 raise util.Abort(_('[keyword] patterns cannot match'))
259 raise util.Abort(_('no [keyword] patterns configured'))
257 raise util.Abort(_('no [keyword] patterns configured'))
260
258
261 def _kwfwrite(ui, repo, expand, *pats, **opts):
259 def _kwfwrite(ui, repo, expand, *pats, **opts):
262 '''Selects files and passes them to kwtemplater.overwrite.'''
260 '''Selects files and passes them to kwtemplater.overwrite.'''
263 if repo.dirstate.parents()[1] != nullid:
261 if repo.dirstate.parents()[1] != nullid:
264 raise util.Abort(_('outstanding uncommitted merge'))
262 raise util.Abort(_('outstanding uncommitted merge'))
265 kwt = kwtools['templater']
263 kwt = kwtools['templater']
266 wlock = repo.wlock()
264 wlock = repo.wlock()
267 try:
265 try:
268 status = _status(ui, repo, kwt, *pats, **opts)
266 status = _status(ui, repo, kwt, *pats, **opts)
269 modified, added, removed, deleted, unknown, ignored, clean = status
267 modified, added, removed, deleted, unknown, ignored, clean = status
270 if modified or added or removed or deleted:
268 if modified or added or removed or deleted:
271 raise util.Abort(_('outstanding uncommitted changes'))
269 raise util.Abort(_('outstanding uncommitted changes'))
272 kwt.overwrite(None, expand, clean)
270 kwt.overwrite(None, expand, clean)
273 finally:
271 finally:
274 wlock.release()
272 wlock.release()
275
273
276 def demo(ui, repo, *args, **opts):
274 def demo(ui, repo, *args, **opts):
277 '''print [keywordmaps] configuration and an expansion example
275 '''print [keywordmaps] configuration and an expansion example
278
276
279 Show current, custom, or default keyword template maps and their
277 Show current, custom, or default keyword template maps and their
280 expansions.
278 expansions.
281
279
282 Extend the current configuration by specifying maps as arguments
280 Extend the current configuration by specifying maps as arguments
283 and using -f/--rcfile to source an external hgrc file.
281 and using -f/--rcfile to source an external hgrc file.
284
282
285 Use -d/--default to disable current configuration.
283 Use -d/--default to disable current configuration.
286
284
287 See "hg help templates" for information on templates and filters.
285 See "hg help templates" for information on templates and filters.
288 '''
286 '''
289 def demoitems(section, items):
287 def demoitems(section, items):
290 ui.write('[%s]\n' % section)
288 ui.write('[%s]\n' % section)
291 for k, v in sorted(items):
289 for k, v in sorted(items):
292 ui.write('%s = %s\n' % (k, v))
290 ui.write('%s = %s\n' % (k, v))
293
291
294 fn = 'demo.txt'
292 fn = 'demo.txt'
295 branchname = 'demobranch'
293 branchname = 'demobranch'
296 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
294 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
297 ui.note(_('creating temporary repository at %s\n') % tmpdir)
295 ui.note(_('creating temporary repository at %s\n') % tmpdir)
298 repo = localrepo.localrepository(ui, tmpdir, True)
296 repo = localrepo.localrepository(ui, tmpdir, True)
299 ui.setconfig('keyword', fn, '')
297 ui.setconfig('keyword', fn, '')
300
298
301 uikwmaps = ui.configitems('keywordmaps')
299 uikwmaps = ui.configitems('keywordmaps')
302 if args or opts.get('rcfile'):
300 if args or opts.get('rcfile'):
303 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
301 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
304 if uikwmaps:
302 if uikwmaps:
305 ui.status(_('\textending current template maps\n'))
303 ui.status(_('\textending current template maps\n'))
306 if opts.get('default') or not uikwmaps:
304 if opts.get('default') or not uikwmaps:
307 ui.status(_('\toverriding default template maps\n'))
305 ui.status(_('\toverriding default template maps\n'))
308 if opts.get('rcfile'):
306 if opts.get('rcfile'):
309 ui.readconfig(opts.get('rcfile'))
307 ui.readconfig(opts.get('rcfile'))
310 if args:
308 if args:
311 # simulate hgrc parsing
309 # simulate hgrc parsing
312 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
310 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
313 fp = repo.opener('hgrc', 'w')
311 fp = repo.opener('hgrc', 'w')
314 fp.writelines(rcmaps)
312 fp.writelines(rcmaps)
315 fp.close()
313 fp.close()
316 ui.readconfig(repo.join('hgrc'))
314 ui.readconfig(repo.join('hgrc'))
317 kwmaps = dict(ui.configitems('keywordmaps'))
315 kwmaps = dict(ui.configitems('keywordmaps'))
318 elif opts.get('default'):
316 elif opts.get('default'):
319 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
317 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
320 kwmaps = kwtemplater.templates
318 kwmaps = kwtemplater.templates
321 if uikwmaps:
319 if uikwmaps:
322 ui.status(_('\tdisabling current template maps\n'))
320 ui.status(_('\tdisabling current template maps\n'))
323 for k, v in kwmaps.iteritems():
321 for k, v in kwmaps.iteritems():
324 ui.setconfig('keywordmaps', k, v)
322 ui.setconfig('keywordmaps', k, v)
325 else:
323 else:
326 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
324 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
327 kwmaps = dict(uikwmaps) or kwtemplater.templates
325 kwmaps = dict(uikwmaps) or kwtemplater.templates
328
326
329 uisetup(ui)
327 uisetup(ui)
330 reposetup(ui, repo)
328 reposetup(ui, repo)
331 for k, v in ui.configitems('extensions'):
329 for k, v in ui.configitems('extensions'):
332 if k.endswith('keyword'):
330 if k.endswith('keyword'):
333 extension = '%s = %s' % (k, v)
331 extension = '%s = %s' % (k, v)
334 break
332 break
335 ui.write('[extensions]\n%s\n' % extension)
333 ui.write('[extensions]\n%s\n' % extension)
336 demoitems('keyword', ui.configitems('keyword'))
334 demoitems('keyword', ui.configitems('keyword'))
337 demoitems('keywordmaps', kwmaps.iteritems())
335 demoitems('keywordmaps', kwmaps.iteritems())
338 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
336 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
339 repo.wopener(fn, 'w').write(keywords)
337 repo.wopener(fn, 'w').write(keywords)
340 repo.add([fn])
338 repo.add([fn])
341 path = repo.wjoin(fn)
339 path = repo.wjoin(fn)
342 ui.note(_('\nkeywords written to %s:\n') % path)
340 ui.note(_('\nkeywords written to %s:\n') % path)
343 ui.note(keywords)
341 ui.note(keywords)
344 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
342 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
345 # silence branch command if not verbose
343 # silence branch command if not verbose
346 quiet = ui.quiet
344 quiet = ui.quiet
347 ui.quiet = not ui.verbose
345 ui.quiet = not ui.verbose
348 commands.branch(ui, repo, branchname)
346 commands.branch(ui, repo, branchname)
349 ui.quiet = quiet
347 ui.quiet = quiet
350 for name, cmd in ui.configitems('hooks'):
348 for name, cmd in ui.configitems('hooks'):
351 if name.split('.', 1)[0].find('commit') > -1:
349 if name.split('.', 1)[0].find('commit') > -1:
352 repo.ui.setconfig('hooks', name, '')
350 repo.ui.setconfig('hooks', name, '')
353 ui.note(_('unhooked all commit hooks\n'))
351 ui.note(_('unhooked all commit hooks\n'))
354 msg = _('hg keyword configuration and expansion example')
352 msg = _('hg keyword configuration and expansion example')
355 ui.note("hg -R '%s' ci -m '%s'\n" % (tmpdir, msg))
353 ui.note("hg -R '%s' ci -m '%s'\n" % (tmpdir, msg))
356 repo.commit(text=msg)
354 repo.commit(text=msg)
357 ui.status(_('\n\tkeywords expanded\n'))
355 ui.status(_('\n\tkeywords expanded\n'))
358 ui.write(repo.wread(fn))
356 ui.write(repo.wread(fn))
359 ui.debug('\nremoving temporary repository %s\n' % tmpdir)
357 ui.debug('\nremoving temporary repository %s\n' % tmpdir)
360 shutil.rmtree(tmpdir, ignore_errors=True)
358 shutil.rmtree(tmpdir, ignore_errors=True)
361
359
362 def expand(ui, repo, *pats, **opts):
360 def expand(ui, repo, *pats, **opts):
363 '''expand keywords in the working directory
361 '''expand keywords in the working directory
364
362
365 Run after (re)enabling keyword expansion.
363 Run after (re)enabling keyword expansion.
366
364
367 kwexpand refuses to run if given files contain local changes.
365 kwexpand refuses to run if given files contain local changes.
368 '''
366 '''
369 # 3rd argument sets expansion to True
367 # 3rd argument sets expansion to True
370 _kwfwrite(ui, repo, True, *pats, **opts)
368 _kwfwrite(ui, repo, True, *pats, **opts)
371
369
372 def files(ui, repo, *pats, **opts):
370 def files(ui, repo, *pats, **opts):
373 '''show files configured for keyword expansion
371 '''show files configured for keyword expansion
374
372
375 List which files in the working directory are matched by the
373 List which files in the working directory are matched by the
376 [keyword] configuration patterns.
374 [keyword] configuration patterns.
377
375
378 Useful to prevent inadvertent keyword expansion and to speed up
376 Useful to prevent inadvertent keyword expansion and to speed up
379 execution by including only files that are actual candidates for
377 execution by including only files that are actual candidates for
380 expansion.
378 expansion.
381
379
382 See "hg help keyword" on how to construct patterns both for
380 See "hg help keyword" on how to construct patterns both for
383 inclusion and exclusion of files.
381 inclusion and exclusion of files.
384
382
385 With -A/--all and -v/--verbose the codes used to show the status
383 With -A/--all and -v/--verbose the codes used to show the status
386 of files are::
384 of files are::
387
385
388 K = keyword expansion candidate
386 K = keyword expansion candidate
389 k = keyword expansion candidate (not tracked)
387 k = keyword expansion candidate (not tracked)
390 I = ignored
388 I = ignored
391 i = ignored (not tracked)
389 i = ignored (not tracked)
392 '''
390 '''
393 kwt = kwtools['templater']
391 kwt = kwtools['templater']
394 status = _status(ui, repo, kwt, *pats, **opts)
392 status = _status(ui, repo, kwt, *pats, **opts)
395 cwd = pats and repo.getcwd() or ''
393 cwd = pats and repo.getcwd() or ''
396 modified, added, removed, deleted, unknown, ignored, clean = status
394 modified, added, removed, deleted, unknown, ignored, clean = status
397 files = []
395 files = []
398 if not (opts.get('unknown') or opts.get('untracked')) or opts.get('all'):
396 if not opts.get('unknown') or opts.get('all'):
399 files = sorted(modified + added + clean)
397 files = sorted(modified + added + clean)
400 wctx = repo[None]
398 wctx = repo[None]
401 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
399 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
402 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
400 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
403 if not opts.get('ignore') or opts.get('all'):
401 if not opts.get('ignore') or opts.get('all'):
404 showfiles = kwfiles, kwunknown
402 showfiles = kwfiles, kwunknown
405 else:
403 else:
406 showfiles = [], []
404 showfiles = [], []
407 if opts.get('all') or opts.get('ignore'):
405 if opts.get('all') or opts.get('ignore'):
408 showfiles += ([f for f in files if f not in kwfiles],
406 showfiles += ([f for f in files if f not in kwfiles],
409 [f for f in unknown if f not in kwunknown])
407 [f for f in unknown if f not in kwunknown])
410 for char, filenames in zip('KkIi', showfiles):
408 for char, filenames in zip('KkIi', showfiles):
411 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
409 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
412 for f in filenames:
410 for f in filenames:
413 ui.write(fmt % repo.pathto(f, cwd))
411 ui.write(fmt % repo.pathto(f, cwd))
414
412
415 def shrink(ui, repo, *pats, **opts):
413 def shrink(ui, repo, *pats, **opts):
416 '''revert expanded keywords in the working directory
414 '''revert expanded keywords in the working directory
417
415
418 Run before changing/disabling active keywords or if you experience
416 Run before changing/disabling active keywords or if you experience
419 problems with "hg import" or "hg merge".
417 problems with "hg import" or "hg merge".
420
418
421 kwshrink refuses to run if given files contain local changes.
419 kwshrink refuses to run if given files contain local changes.
422 '''
420 '''
423 # 3rd argument sets expansion to False
421 # 3rd argument sets expansion to False
424 _kwfwrite(ui, repo, False, *pats, **opts)
422 _kwfwrite(ui, repo, False, *pats, **opts)
425
423
426
424
427 def uisetup(ui):
425 def uisetup(ui):
428 '''Collects [keyword] config in kwtools.
426 '''Collects [keyword] config in kwtools.
429 Monkeypatches dispatch._parse if needed.'''
427 Monkeypatches dispatch._parse if needed.'''
430
428
431 for pat, opt in ui.configitems('keyword'):
429 for pat, opt in ui.configitems('keyword'):
432 if opt != 'ignore':
430 if opt != 'ignore':
433 kwtools['inc'].append(pat)
431 kwtools['inc'].append(pat)
434 else:
432 else:
435 kwtools['exc'].append(pat)
433 kwtools['exc'].append(pat)
436
434
437 if kwtools['inc']:
435 if kwtools['inc']:
438 def kwdispatch_parse(orig, ui, args):
436 def kwdispatch_parse(orig, ui, args):
439 '''Monkeypatch dispatch._parse to obtain running hg command.'''
437 '''Monkeypatch dispatch._parse to obtain running hg command.'''
440 cmd, func, args, options, cmdoptions = orig(ui, args)
438 cmd, func, args, options, cmdoptions = orig(ui, args)
441 kwtools['hgcmd'] = cmd
439 kwtools['hgcmd'] = cmd
442 return cmd, func, args, options, cmdoptions
440 return cmd, func, args, options, cmdoptions
443
441
444 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
442 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
445
443
446 def reposetup(ui, repo):
444 def reposetup(ui, repo):
447 '''Sets up repo as kwrepo for keyword substitution.
445 '''Sets up repo as kwrepo for keyword substitution.
448 Overrides file method to return kwfilelog instead of filelog
446 Overrides file method to return kwfilelog instead of filelog
449 if file matches user configuration.
447 if file matches user configuration.
450 Wraps commit to overwrite configured files with updated
448 Wraps commit to overwrite configured files with updated
451 keyword substitutions.
449 keyword substitutions.
452 Monkeypatches patch and webcommands.'''
450 Monkeypatches patch and webcommands.'''
453
451
454 try:
452 try:
455 if (not repo.local() or not kwtools['inc']
453 if (not repo.local() or not kwtools['inc']
456 or kwtools['hgcmd'] in nokwcommands.split()
454 or kwtools['hgcmd'] in nokwcommands.split()
457 or '.hg' in util.splitpath(repo.root)
455 or '.hg' in util.splitpath(repo.root)
458 or repo._url.startswith('bundle:')):
456 or repo._url.startswith('bundle:')):
459 return
457 return
460 except AttributeError:
458 except AttributeError:
461 pass
459 pass
462
460
463 kwtools['templater'] = kwt = kwtemplater(ui, repo)
461 kwtools['templater'] = kwt = kwtemplater(ui, repo)
464
462
465 class kwrepo(repo.__class__):
463 class kwrepo(repo.__class__):
466 def file(self, f):
464 def file(self, f):
467 if f[0] == '/':
465 if f[0] == '/':
468 f = f[1:]
466 f = f[1:]
469 return kwfilelog(self.sopener, kwt, f)
467 return kwfilelog(self.sopener, kwt, f)
470
468
471 def wread(self, filename):
469 def wread(self, filename):
472 data = super(kwrepo, self).wread(filename)
470 data = super(kwrepo, self).wread(filename)
473 return kwt.wread(filename, data)
471 return kwt.wread(filename, data)
474
472
475 def commit(self, *args, **opts):
473 def commit(self, *args, **opts):
476 # use custom commitctx for user commands
474 # use custom commitctx for user commands
477 # other extensions can still wrap repo.commitctx directly
475 # other extensions can still wrap repo.commitctx directly
478 self.commitctx = self.kwcommitctx
476 self.commitctx = self.kwcommitctx
479 try:
477 try:
480 return super(kwrepo, self).commit(*args, **opts)
478 return super(kwrepo, self).commit(*args, **opts)
481 finally:
479 finally:
482 del self.commitctx
480 del self.commitctx
483
481
484 def kwcommitctx(self, ctx, error=False):
482 def kwcommitctx(self, ctx, error=False):
485 n = super(kwrepo, self).commitctx(ctx, error)
483 n = super(kwrepo, self).commitctx(ctx, error)
486 # no lock needed, only called from repo.commit() which already locks
484 # no lock needed, only called from repo.commit() which already locks
487 kwt.overwrite(n, True, None)
485 kwt.overwrite(n, True, None)
488 return n
486 return n
489
487
490 # monkeypatches
488 # monkeypatches
491 def kwpatchfile_init(orig, self, ui, fname, opener,
489 def kwpatchfile_init(orig, self, ui, fname, opener,
492 missing=False, eol=None):
490 missing=False, eol=None):
493 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
491 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
494 rejects or conflicts due to expanded keywords in working dir.'''
492 rejects or conflicts due to expanded keywords in working dir.'''
495 orig(self, ui, fname, opener, missing, eol)
493 orig(self, ui, fname, opener, missing, eol)
496 # shrink keywords read from working dir
494 # shrink keywords read from working dir
497 self.lines = kwt.shrinklines(self.fname, self.lines)
495 self.lines = kwt.shrinklines(self.fname, self.lines)
498
496
499 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
497 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
500 opts=None):
498 opts=None):
501 '''Monkeypatch patch.diff to avoid expansion except when
499 '''Monkeypatch patch.diff to avoid expansion except when
502 comparing against working dir.'''
500 comparing against working dir.'''
503 if node2 is not None:
501 if node2 is not None:
504 kwt.match = util.never
502 kwt.match = util.never
505 elif node1 is not None and node1 != repo['.'].node():
503 elif node1 is not None and node1 != repo['.'].node():
506 kwt.restrict = True
504 kwt.restrict = True
507 return orig(repo, node1, node2, match, changes, opts)
505 return orig(repo, node1, node2, match, changes, opts)
508
506
509 def kwweb_skip(orig, web, req, tmpl):
507 def kwweb_skip(orig, web, req, tmpl):
510 '''Wraps webcommands.x turning off keyword expansion.'''
508 '''Wraps webcommands.x turning off keyword expansion.'''
511 kwt.match = util.never
509 kwt.match = util.never
512 return orig(web, req, tmpl)
510 return orig(web, req, tmpl)
513
511
514 repo.__class__ = kwrepo
512 repo.__class__ = kwrepo
515
513
516 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
514 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
517 if not kwt.restrict:
515 if not kwt.restrict:
518 extensions.wrapfunction(patch, 'diff', kw_diff)
516 extensions.wrapfunction(patch, 'diff', kw_diff)
519 for c in 'annotate changeset rev filediff diff'.split():
517 for c in 'annotate changeset rev filediff diff'.split():
520 extensions.wrapfunction(webcommands, c, kwweb_skip)
518 extensions.wrapfunction(webcommands, c, kwweb_skip)
521
519
522 cmdtable = {
520 cmdtable = {
523 'kwdemo':
521 'kwdemo':
524 (demo,
522 (demo,
525 [('d', 'default', None, _('show default keyword template maps')),
523 [('d', 'default', None, _('show default keyword template maps')),
526 ('f', 'rcfile', '', _('read maps from rcfile'))],
524 ('f', 'rcfile', '', _('read maps from rcfile'))],
527 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
525 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
528 'kwexpand': (expand, commands.walkopts,
526 'kwexpand': (expand, commands.walkopts,
529 _('hg kwexpand [OPTION]... [FILE]...')),
527 _('hg kwexpand [OPTION]... [FILE]...')),
530 'kwfiles':
528 'kwfiles':
531 (files,
529 (files,
532 [('A', 'all', None, _('show keyword status flags of all files')),
530 [('A', 'all', None, _('show keyword status flags of all files')),
533 ('i', 'ignore', None, _('show files excluded from expansion')),
531 ('i', 'ignore', None, _('show files excluded from expansion')),
534 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
532 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
535 ('a', 'all', None,
536 _('show keyword status flags of all files (DEPRECATED)')),
537 ('u', 'untracked', None, _('only show untracked files (DEPRECATED)')),
538 ] + commands.walkopts,
533 ] + commands.walkopts,
539 _('hg kwfiles [OPTION]... [FILE]...')),
534 _('hg kwfiles [OPTION]... [FILE]...')),
540 'kwshrink': (shrink, commands.walkopts,
535 'kwshrink': (shrink, commands.walkopts,
541 _('hg kwshrink [OPTION]... [FILE]...')),
536 _('hg kwshrink [OPTION]... [FILE]...')),
542 }
537 }
General Comments 0
You need to be logged in to leave comments. Login now