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