##// END OF EJS Templates
keyword: make kwdemo less verbose...
Christian Ebert -
r10713:b9c3f8e8 default
parent child Browse files
Show More
@@ -1,534 +1,525
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 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, candidates):
165 def overwrite(self, node, expand, candidates):
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 candidates = [f for f in ctx.files() if f in mf]
170 candidates = [f for f in ctx.files() if f in mf]
171 candidates = [f for f in candidates if self.iskwfile(f, ctx.flags)]
171 candidates = [f for f in candidates if self.iskwfile(f, ctx.flags)]
172 if candidates:
172 if candidates:
173 self.restrict = True # do not expand when reading
173 self.restrict = True # do not expand when reading
174 msg = (expand and _('overwriting %s expanding keywords\n')
174 msg = (expand and _('overwriting %s expanding keywords\n')
175 or _('overwriting %s shrinking keywords\n'))
175 or _('overwriting %s shrinking keywords\n'))
176 for f in candidates:
176 for f in candidates:
177 fp = self.repo.file(f)
177 fp = self.repo.file(f)
178 data = fp.read(mf[f])
178 data = fp.read(mf[f])
179 if util.binary(data):
179 if util.binary(data):
180 continue
180 continue
181 if expand:
181 if expand:
182 if node is None:
182 if node is None:
183 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
183 ctx = self.repo.filectx(f, fileid=mf[f]).changectx()
184 data, found = self.substitute(data, f, ctx,
184 data, found = self.substitute(data, f, ctx,
185 self.re_kw.subn)
185 self.re_kw.subn)
186 else:
186 else:
187 found = self.re_kw.search(data)
187 found = self.re_kw.search(data)
188 if found:
188 if found:
189 self.ui.note(msg % f)
189 self.ui.note(msg % f)
190 self.repo.wwrite(f, data, mf.flags(f))
190 self.repo.wwrite(f, data, mf.flags(f))
191 if node is None:
191 if node is None:
192 self.repo.dirstate.normal(f)
192 self.repo.dirstate.normal(f)
193 self.restrict = False
193 self.restrict = False
194
194
195 def shrinktext(self, text):
195 def shrinktext(self, text):
196 '''Unconditionally removes all keyword substitutions from text.'''
196 '''Unconditionally removes all keyword substitutions from text.'''
197 return self.re_kw.sub(r'$\1$', text)
197 return self.re_kw.sub(r'$\1$', text)
198
198
199 def shrink(self, fname, text):
199 def shrink(self, fname, text):
200 '''Returns text with all keyword substitutions removed.'''
200 '''Returns text with all keyword substitutions removed.'''
201 if self.match(fname) and not util.binary(text):
201 if self.match(fname) and not util.binary(text):
202 return self.shrinktext(text)
202 return self.shrinktext(text)
203 return text
203 return text
204
204
205 def shrinklines(self, fname, lines):
205 def shrinklines(self, fname, lines):
206 '''Returns lines with keyword substitutions removed.'''
206 '''Returns lines with keyword substitutions removed.'''
207 if self.match(fname):
207 if self.match(fname):
208 text = ''.join(lines)
208 text = ''.join(lines)
209 if not util.binary(text):
209 if not util.binary(text):
210 return self.shrinktext(text).splitlines(True)
210 return self.shrinktext(text).splitlines(True)
211 return lines
211 return lines
212
212
213 def wread(self, fname, data):
213 def wread(self, fname, data):
214 '''If in restricted mode returns data read from wdir with
214 '''If in restricted mode returns data read from wdir with
215 keyword substitutions removed.'''
215 keyword substitutions removed.'''
216 return self.restrict and self.shrink(fname, data) or data
216 return self.restrict and self.shrink(fname, data) or data
217
217
218 class kwfilelog(filelog.filelog):
218 class kwfilelog(filelog.filelog):
219 '''
219 '''
220 Subclass of filelog to hook into its read, add, cmp methods.
220 Subclass of filelog to hook into its read, add, cmp methods.
221 Keywords are "stored" unexpanded, and processed on reading.
221 Keywords are "stored" unexpanded, and processed on reading.
222 '''
222 '''
223 def __init__(self, opener, kwt, path):
223 def __init__(self, opener, kwt, path):
224 super(kwfilelog, self).__init__(opener, path)
224 super(kwfilelog, self).__init__(opener, path)
225 self.kwt = kwt
225 self.kwt = kwt
226 self.path = path
226 self.path = path
227
227
228 def read(self, node):
228 def read(self, node):
229 '''Expands keywords when reading filelog.'''
229 '''Expands keywords when reading filelog.'''
230 data = super(kwfilelog, self).read(node)
230 data = super(kwfilelog, self).read(node)
231 return self.kwt.expand(self.path, node, data)
231 return self.kwt.expand(self.path, node, data)
232
232
233 def add(self, text, meta, tr, link, p1=None, p2=None):
233 def add(self, text, meta, tr, link, p1=None, p2=None):
234 '''Removes keyword substitutions when adding to filelog.'''
234 '''Removes keyword substitutions when adding to filelog.'''
235 text = self.kwt.shrink(self.path, text)
235 text = self.kwt.shrink(self.path, text)
236 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
236 return super(kwfilelog, self).add(text, meta, tr, link, p1, p2)
237
237
238 def cmp(self, node, text):
238 def cmp(self, node, text):
239 '''Removes keyword substitutions for comparison.'''
239 '''Removes keyword substitutions for comparison.'''
240 text = self.kwt.shrink(self.path, text)
240 text = self.kwt.shrink(self.path, text)
241 if self.renamed(node):
241 if self.renamed(node):
242 t2 = super(kwfilelog, self).read(node)
242 t2 = super(kwfilelog, self).read(node)
243 return t2 != text
243 return t2 != text
244 return revlog.revlog.cmp(self, node, text)
244 return revlog.revlog.cmp(self, node, text)
245
245
246 def _status(ui, repo, kwt, *pats, **opts):
246 def _status(ui, repo, kwt, *pats, **opts):
247 '''Bails out if [keyword] configuration is not active.
247 '''Bails out if [keyword] configuration is not active.
248 Returns status of working directory.'''
248 Returns status of working directory.'''
249 if kwt:
249 if kwt:
250 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
250 return repo.status(match=cmdutil.match(repo, pats, opts), clean=True,
251 unknown=opts.get('unknown') or opts.get('all'))
251 unknown=opts.get('unknown') or opts.get('all'))
252 if ui.configitems('keyword'):
252 if ui.configitems('keyword'):
253 raise util.Abort(_('[keyword] patterns cannot match'))
253 raise util.Abort(_('[keyword] patterns cannot match'))
254 raise util.Abort(_('no [keyword] patterns configured'))
254 raise util.Abort(_('no [keyword] patterns configured'))
255
255
256 def _kwfwrite(ui, repo, expand, *pats, **opts):
256 def _kwfwrite(ui, repo, expand, *pats, **opts):
257 '''Selects files and passes them to kwtemplater.overwrite.'''
257 '''Selects files and passes them to kwtemplater.overwrite.'''
258 if repo.dirstate.parents()[1] != nullid:
258 if repo.dirstate.parents()[1] != nullid:
259 raise util.Abort(_('outstanding uncommitted merge'))
259 raise util.Abort(_('outstanding uncommitted merge'))
260 kwt = kwtools['templater']
260 kwt = kwtools['templater']
261 wlock = repo.wlock()
261 wlock = repo.wlock()
262 try:
262 try:
263 status = _status(ui, repo, kwt, *pats, **opts)
263 status = _status(ui, repo, kwt, *pats, **opts)
264 modified, added, removed, deleted, unknown, ignored, clean = status
264 modified, added, removed, deleted, unknown, ignored, clean = status
265 if modified or added or removed or deleted:
265 if modified or added or removed or deleted:
266 raise util.Abort(_('outstanding uncommitted changes'))
266 raise util.Abort(_('outstanding uncommitted changes'))
267 kwt.overwrite(None, expand, clean)
267 kwt.overwrite(None, expand, clean)
268 finally:
268 finally:
269 wlock.release()
269 wlock.release()
270
270
271 def demo(ui, repo, *args, **opts):
271 def demo(ui, repo, *args, **opts):
272 '''print [keywordmaps] configuration and an expansion example
272 '''print [keywordmaps] configuration and an expansion example
273
273
274 Show current, custom, or default keyword template maps and their
274 Show current, custom, or default keyword template maps and their
275 expansions.
275 expansions.
276
276
277 Extend the current configuration by specifying maps as arguments
277 Extend the current configuration by specifying maps as arguments
278 and using -f/--rcfile to source an external hgrc file.
278 and using -f/--rcfile to source an external hgrc file.
279
279
280 Use -d/--default to disable current configuration.
280 Use -d/--default to disable current configuration.
281
281
282 See "hg help templates" for information on templates and filters.
282 See "hg help templates" for information on templates and filters.
283 '''
283 '''
284 def demoitems(section, items):
284 def demoitems(section, items):
285 ui.write('[%s]\n' % section)
285 ui.write('[%s]\n' % section)
286 for k, v in sorted(items):
286 for k, v in sorted(items):
287 ui.write('%s = %s\n' % (k, v))
287 ui.write('%s = %s\n' % (k, v))
288
288
289 fn = 'demo.txt'
289 fn = 'demo.txt'
290 branchname = 'demobranch'
291 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
290 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
292 ui.note(_('creating temporary repository at %s\n') % tmpdir)
291 ui.note(_('creating temporary repository at %s\n') % tmpdir)
293 repo = localrepo.localrepository(ui, tmpdir, True)
292 repo = localrepo.localrepository(ui, tmpdir, True)
294 ui.setconfig('keyword', fn, '')
293 ui.setconfig('keyword', fn, '')
295
294
296 uikwmaps = ui.configitems('keywordmaps')
295 uikwmaps = ui.configitems('keywordmaps')
297 if args or opts.get('rcfile'):
296 if args or opts.get('rcfile'):
298 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
297 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
299 if uikwmaps:
298 if uikwmaps:
300 ui.status(_('\textending current template maps\n'))
299 ui.status(_('\textending current template maps\n'))
301 if opts.get('default') or not uikwmaps:
300 if opts.get('default') or not uikwmaps:
302 ui.status(_('\toverriding default template maps\n'))
301 ui.status(_('\toverriding default template maps\n'))
303 if opts.get('rcfile'):
302 if opts.get('rcfile'):
304 ui.readconfig(opts.get('rcfile'))
303 ui.readconfig(opts.get('rcfile'))
305 if args:
304 if args:
306 # simulate hgrc parsing
305 # simulate hgrc parsing
307 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
306 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
308 fp = repo.opener('hgrc', 'w')
307 fp = repo.opener('hgrc', 'w')
309 fp.writelines(rcmaps)
308 fp.writelines(rcmaps)
310 fp.close()
309 fp.close()
311 ui.readconfig(repo.join('hgrc'))
310 ui.readconfig(repo.join('hgrc'))
312 kwmaps = dict(ui.configitems('keywordmaps'))
311 kwmaps = dict(ui.configitems('keywordmaps'))
313 elif opts.get('default'):
312 elif opts.get('default'):
314 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
313 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
315 kwmaps = kwtemplater.templates
314 kwmaps = kwtemplater.templates
316 if uikwmaps:
315 if uikwmaps:
317 ui.status(_('\tdisabling current template maps\n'))
316 ui.status(_('\tdisabling current template maps\n'))
318 for k, v in kwmaps.iteritems():
317 for k, v in kwmaps.iteritems():
319 ui.setconfig('keywordmaps', k, v)
318 ui.setconfig('keywordmaps', k, v)
320 else:
319 else:
321 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
320 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
322 kwmaps = dict(uikwmaps) or kwtemplater.templates
321 kwmaps = dict(uikwmaps) or kwtemplater.templates
323
322
324 uisetup(ui)
323 uisetup(ui)
325 reposetup(ui, repo)
324 reposetup(ui, repo)
326 for k, v in ui.configitems('extensions'):
325 for k, v in ui.configitems('extensions'):
327 if k.endswith('keyword'):
326 if k.endswith('keyword'):
328 extension = '%s = %s' % (k, v)
327 extension = '%s = %s' % (k, v)
329 break
328 break
330 ui.write('[extensions]\n%s\n' % extension)
329 ui.write('[extensions]\n%s\n' % extension)
331 demoitems('keyword', ui.configitems('keyword'))
330 demoitems('keyword', ui.configitems('keyword'))
332 demoitems('keywordmaps', kwmaps.iteritems())
331 demoitems('keywordmaps', kwmaps.iteritems())
333 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
332 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
334 repo.wopener(fn, 'w').write(keywords)
333 repo.wopener(fn, 'w').write(keywords)
335 repo.add([fn])
334 repo.add([fn])
336 path = repo.wjoin(fn)
335 ui.note(_('\nkeywords written to %s:\n') % fn)
337 ui.note(_('\nkeywords written to %s:\n') % path)
338 ui.note(keywords)
336 ui.note(keywords)
339 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
337 repo.dirstate.setbranch('demobranch')
340 # silence branch command if not verbose
341 quiet = ui.quiet
342 ui.quiet = not ui.verbose
343 commands.branch(ui, repo, branchname)
344 ui.quiet = quiet
345 for name, cmd in ui.configitems('hooks'):
338 for name, cmd in ui.configitems('hooks'):
346 if name.split('.', 1)[0].find('commit') > -1:
339 if name.split('.', 1)[0].find('commit') > -1:
347 repo.ui.setconfig('hooks', name, '')
340 repo.ui.setconfig('hooks', name, '')
348 ui.note(_('unhooked all commit hooks\n'))
349 msg = _('hg keyword configuration and expansion example')
341 msg = _('hg keyword configuration and expansion example')
350 ui.note("hg -R '%s' ci -m '%s'\n" % (tmpdir, msg))
342 ui.note("hg ci -m '%s'\n" % msg)
351 repo.commit(text=msg)
343 repo.commit(text=msg)
352 ui.status(_('\n\tkeywords expanded\n'))
344 ui.status(_('\n\tkeywords expanded\n'))
353 ui.write(repo.wread(fn))
345 ui.write(repo.wread(fn))
354 ui.debug('\nremoving temporary repository %s\n' % tmpdir)
355 shutil.rmtree(tmpdir, ignore_errors=True)
346 shutil.rmtree(tmpdir, ignore_errors=True)
356
347
357 def expand(ui, repo, *pats, **opts):
348 def expand(ui, repo, *pats, **opts):
358 '''expand keywords in the working directory
349 '''expand keywords in the working directory
359
350
360 Run after (re)enabling keyword expansion.
351 Run after (re)enabling keyword expansion.
361
352
362 kwexpand refuses to run if given files contain local changes.
353 kwexpand refuses to run if given files contain local changes.
363 '''
354 '''
364 # 3rd argument sets expansion to True
355 # 3rd argument sets expansion to True
365 _kwfwrite(ui, repo, True, *pats, **opts)
356 _kwfwrite(ui, repo, True, *pats, **opts)
366
357
367 def files(ui, repo, *pats, **opts):
358 def files(ui, repo, *pats, **opts):
368 '''show files configured for keyword expansion
359 '''show files configured for keyword expansion
369
360
370 List which files in the working directory are matched by the
361 List which files in the working directory are matched by the
371 [keyword] configuration patterns.
362 [keyword] configuration patterns.
372
363
373 Useful to prevent inadvertent keyword expansion and to speed up
364 Useful to prevent inadvertent keyword expansion and to speed up
374 execution by including only files that are actual candidates for
365 execution by including only files that are actual candidates for
375 expansion.
366 expansion.
376
367
377 See "hg help keyword" on how to construct patterns both for
368 See "hg help keyword" on how to construct patterns both for
378 inclusion and exclusion of files.
369 inclusion and exclusion of files.
379
370
380 With -A/--all and -v/--verbose the codes used to show the status
371 With -A/--all and -v/--verbose the codes used to show the status
381 of files are::
372 of files are::
382
373
383 K = keyword expansion candidate
374 K = keyword expansion candidate
384 k = keyword expansion candidate (not tracked)
375 k = keyword expansion candidate (not tracked)
385 I = ignored
376 I = ignored
386 i = ignored (not tracked)
377 i = ignored (not tracked)
387 '''
378 '''
388 kwt = kwtools['templater']
379 kwt = kwtools['templater']
389 status = _status(ui, repo, kwt, *pats, **opts)
380 status = _status(ui, repo, kwt, *pats, **opts)
390 cwd = pats and repo.getcwd() or ''
381 cwd = pats and repo.getcwd() or ''
391 modified, added, removed, deleted, unknown, ignored, clean = status
382 modified, added, removed, deleted, unknown, ignored, clean = status
392 files = []
383 files = []
393 if not opts.get('unknown') or opts.get('all'):
384 if not opts.get('unknown') or opts.get('all'):
394 files = sorted(modified + added + clean)
385 files = sorted(modified + added + clean)
395 wctx = repo[None]
386 wctx = repo[None]
396 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
387 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
397 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
388 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
398 if not opts.get('ignore') or opts.get('all'):
389 if not opts.get('ignore') or opts.get('all'):
399 showfiles = kwfiles, kwunknown
390 showfiles = kwfiles, kwunknown
400 else:
391 else:
401 showfiles = [], []
392 showfiles = [], []
402 if opts.get('all') or opts.get('ignore'):
393 if opts.get('all') or opts.get('ignore'):
403 showfiles += ([f for f in files if f not in kwfiles],
394 showfiles += ([f for f in files if f not in kwfiles],
404 [f for f in unknown if f not in kwunknown])
395 [f for f in unknown if f not in kwunknown])
405 for char, filenames in zip('KkIi', showfiles):
396 for char, filenames in zip('KkIi', showfiles):
406 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
397 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
407 for f in filenames:
398 for f in filenames:
408 ui.write(fmt % repo.pathto(f, cwd))
399 ui.write(fmt % repo.pathto(f, cwd))
409
400
410 def shrink(ui, repo, *pats, **opts):
401 def shrink(ui, repo, *pats, **opts):
411 '''revert expanded keywords in the working directory
402 '''revert expanded keywords in the working directory
412
403
413 Run before changing/disabling active keywords or if you experience
404 Run before changing/disabling active keywords or if you experience
414 problems with "hg import" or "hg merge".
405 problems with "hg import" or "hg merge".
415
406
416 kwshrink refuses to run if given files contain local changes.
407 kwshrink refuses to run if given files contain local changes.
417 '''
408 '''
418 # 3rd argument sets expansion to False
409 # 3rd argument sets expansion to False
419 _kwfwrite(ui, repo, False, *pats, **opts)
410 _kwfwrite(ui, repo, False, *pats, **opts)
420
411
421
412
422 def uisetup(ui):
413 def uisetup(ui):
423 '''Collects [keyword] config in kwtools.
414 '''Collects [keyword] config in kwtools.
424 Monkeypatches dispatch._parse if needed.'''
415 Monkeypatches dispatch._parse if needed.'''
425
416
426 for pat, opt in ui.configitems('keyword'):
417 for pat, opt in ui.configitems('keyword'):
427 if opt != 'ignore':
418 if opt != 'ignore':
428 kwtools['inc'].append(pat)
419 kwtools['inc'].append(pat)
429 else:
420 else:
430 kwtools['exc'].append(pat)
421 kwtools['exc'].append(pat)
431
422
432 if kwtools['inc']:
423 if kwtools['inc']:
433 def kwdispatch_parse(orig, ui, args):
424 def kwdispatch_parse(orig, ui, args):
434 '''Monkeypatch dispatch._parse to obtain running hg command.'''
425 '''Monkeypatch dispatch._parse to obtain running hg command.'''
435 cmd, func, args, options, cmdoptions = orig(ui, args)
426 cmd, func, args, options, cmdoptions = orig(ui, args)
436 kwtools['hgcmd'] = cmd
427 kwtools['hgcmd'] = cmd
437 return cmd, func, args, options, cmdoptions
428 return cmd, func, args, options, cmdoptions
438
429
439 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
430 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
440
431
441 def reposetup(ui, repo):
432 def reposetup(ui, repo):
442 '''Sets up repo as kwrepo for keyword substitution.
433 '''Sets up repo as kwrepo for keyword substitution.
443 Overrides file method to return kwfilelog instead of filelog
434 Overrides file method to return kwfilelog instead of filelog
444 if file matches user configuration.
435 if file matches user configuration.
445 Wraps commit to overwrite configured files with updated
436 Wraps commit to overwrite configured files with updated
446 keyword substitutions.
437 keyword substitutions.
447 Monkeypatches patch and webcommands.'''
438 Monkeypatches patch and webcommands.'''
448
439
449 try:
440 try:
450 if (not repo.local() or not kwtools['inc']
441 if (not repo.local() or not kwtools['inc']
451 or kwtools['hgcmd'] in nokwcommands.split()
442 or kwtools['hgcmd'] in nokwcommands.split()
452 or '.hg' in util.splitpath(repo.root)
443 or '.hg' in util.splitpath(repo.root)
453 or repo._url.startswith('bundle:')):
444 or repo._url.startswith('bundle:')):
454 return
445 return
455 except AttributeError:
446 except AttributeError:
456 pass
447 pass
457
448
458 kwtools['templater'] = kwt = kwtemplater(ui, repo)
449 kwtools['templater'] = kwt = kwtemplater(ui, repo)
459
450
460 class kwrepo(repo.__class__):
451 class kwrepo(repo.__class__):
461 def file(self, f):
452 def file(self, f):
462 if f[0] == '/':
453 if f[0] == '/':
463 f = f[1:]
454 f = f[1:]
464 return kwfilelog(self.sopener, kwt, f)
455 return kwfilelog(self.sopener, kwt, f)
465
456
466 def wread(self, filename):
457 def wread(self, filename):
467 data = super(kwrepo, self).wread(filename)
458 data = super(kwrepo, self).wread(filename)
468 return kwt.wread(filename, data)
459 return kwt.wread(filename, data)
469
460
470 def commit(self, *args, **opts):
461 def commit(self, *args, **opts):
471 # use custom commitctx for user commands
462 # use custom commitctx for user commands
472 # other extensions can still wrap repo.commitctx directly
463 # other extensions can still wrap repo.commitctx directly
473 self.commitctx = self.kwcommitctx
464 self.commitctx = self.kwcommitctx
474 try:
465 try:
475 return super(kwrepo, self).commit(*args, **opts)
466 return super(kwrepo, self).commit(*args, **opts)
476 finally:
467 finally:
477 del self.commitctx
468 del self.commitctx
478
469
479 def kwcommitctx(self, ctx, error=False):
470 def kwcommitctx(self, ctx, error=False):
480 n = super(kwrepo, self).commitctx(ctx, error)
471 n = super(kwrepo, self).commitctx(ctx, error)
481 # no lock needed, only called from repo.commit() which already locks
472 # no lock needed, only called from repo.commit() which already locks
482 kwt.overwrite(n, True, None)
473 kwt.overwrite(n, True, None)
483 return n
474 return n
484
475
485 # monkeypatches
476 # monkeypatches
486 def kwpatchfile_init(orig, self, ui, fname, opener,
477 def kwpatchfile_init(orig, self, ui, fname, opener,
487 missing=False, eol=None):
478 missing=False, eol=None):
488 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
479 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
489 rejects or conflicts due to expanded keywords in working dir.'''
480 rejects or conflicts due to expanded keywords in working dir.'''
490 orig(self, ui, fname, opener, missing, eol)
481 orig(self, ui, fname, opener, missing, eol)
491 # shrink keywords read from working dir
482 # shrink keywords read from working dir
492 self.lines = kwt.shrinklines(self.fname, self.lines)
483 self.lines = kwt.shrinklines(self.fname, self.lines)
493
484
494 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
485 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
495 opts=None):
486 opts=None):
496 '''Monkeypatch patch.diff to avoid expansion except when
487 '''Monkeypatch patch.diff to avoid expansion except when
497 comparing against working dir.'''
488 comparing against working dir.'''
498 if node2 is not None:
489 if node2 is not None:
499 kwt.match = util.never
490 kwt.match = util.never
500 elif node1 is not None and node1 != repo['.'].node():
491 elif node1 is not None and node1 != repo['.'].node():
501 kwt.restrict = True
492 kwt.restrict = True
502 return orig(repo, node1, node2, match, changes, opts)
493 return orig(repo, node1, node2, match, changes, opts)
503
494
504 def kwweb_skip(orig, web, req, tmpl):
495 def kwweb_skip(orig, web, req, tmpl):
505 '''Wraps webcommands.x turning off keyword expansion.'''
496 '''Wraps webcommands.x turning off keyword expansion.'''
506 kwt.match = util.never
497 kwt.match = util.never
507 return orig(web, req, tmpl)
498 return orig(web, req, tmpl)
508
499
509 repo.__class__ = kwrepo
500 repo.__class__ = kwrepo
510
501
511 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
502 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
512 if not kwt.restrict:
503 if not kwt.restrict:
513 extensions.wrapfunction(patch, 'diff', kw_diff)
504 extensions.wrapfunction(patch, 'diff', kw_diff)
514 for c in 'annotate changeset rev filediff diff'.split():
505 for c in 'annotate changeset rev filediff diff'.split():
515 extensions.wrapfunction(webcommands, c, kwweb_skip)
506 extensions.wrapfunction(webcommands, c, kwweb_skip)
516
507
517 cmdtable = {
508 cmdtable = {
518 'kwdemo':
509 'kwdemo':
519 (demo,
510 (demo,
520 [('d', 'default', None, _('show default keyword template maps')),
511 [('d', 'default', None, _('show default keyword template maps')),
521 ('f', 'rcfile', '', _('read maps from rcfile'))],
512 ('f', 'rcfile', '', _('read maps from rcfile'))],
522 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
513 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
523 'kwexpand': (expand, commands.walkopts,
514 'kwexpand': (expand, commands.walkopts,
524 _('hg kwexpand [OPTION]... [FILE]...')),
515 _('hg kwexpand [OPTION]... [FILE]...')),
525 'kwfiles':
516 'kwfiles':
526 (files,
517 (files,
527 [('A', 'all', None, _('show keyword status flags of all files')),
518 [('A', 'all', None, _('show keyword status flags of all files')),
528 ('i', 'ignore', None, _('show files excluded from expansion')),
519 ('i', 'ignore', None, _('show files excluded from expansion')),
529 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
520 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
530 ] + commands.walkopts,
521 ] + commands.walkopts,
531 _('hg kwfiles [OPTION]... [FILE]...')),
522 _('hg kwfiles [OPTION]... [FILE]...')),
532 'kwshrink': (shrink, commands.walkopts,
523 'kwshrink': (shrink, commands.walkopts,
533 _('hg kwshrink [OPTION]... [FILE]...')),
524 _('hg kwshrink [OPTION]... [FILE]...')),
534 }
525 }
General Comments 0
You need to be logged in to leave comments. Login now