##// END OF EJS Templates
keyword: do not customize ui.note() when overwriting...
Christian Ebert -
r10711:2df04488 default
parent child Browse files
Show More
@@ -1,537 +1,534 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 "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
172 else: # kwexpand/kwshrink
173 notify = self.ui.note
174 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
171 candidates = [f for f in files if self.iskwfile(f, ctx.flags)]
175 if candidates:
172 if candidates:
176 self.restrict = True # do not expand when reading
173 self.restrict = True # do not expand when reading
177 msg = (expand and _('overwriting %s expanding keywords\n')
174 msg = (expand and _('overwriting %s expanding keywords\n')
178 or _('overwriting %s shrinking keywords\n'))
175 or _('overwriting %s shrinking keywords\n'))
179 for f in candidates:
176 for f in candidates:
180 fp = self.repo.file(f)
177 fp = self.repo.file(f)
181 data = fp.read(mf[f])
178 data = fp.read(mf[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 notify(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 branchname = 'demobranch'
290 branchname = 'demobranch'
294 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
291 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
295 ui.note(_('creating temporary repository at %s\n') % tmpdir)
292 ui.note(_('creating temporary repository at %s\n') % tmpdir)
296 repo = localrepo.localrepository(ui, tmpdir, True)
293 repo = localrepo.localrepository(ui, tmpdir, True)
297 ui.setconfig('keyword', fn, '')
294 ui.setconfig('keyword', fn, '')
298
295
299 uikwmaps = ui.configitems('keywordmaps')
296 uikwmaps = ui.configitems('keywordmaps')
300 if args or opts.get('rcfile'):
297 if args or opts.get('rcfile'):
301 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
298 ui.status(_('\n\tconfiguration using custom keyword template maps\n'))
302 if uikwmaps:
299 if uikwmaps:
303 ui.status(_('\textending current template maps\n'))
300 ui.status(_('\textending current template maps\n'))
304 if opts.get('default') or not uikwmaps:
301 if opts.get('default') or not uikwmaps:
305 ui.status(_('\toverriding default template maps\n'))
302 ui.status(_('\toverriding default template maps\n'))
306 if opts.get('rcfile'):
303 if opts.get('rcfile'):
307 ui.readconfig(opts.get('rcfile'))
304 ui.readconfig(opts.get('rcfile'))
308 if args:
305 if args:
309 # simulate hgrc parsing
306 # simulate hgrc parsing
310 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
307 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
311 fp = repo.opener('hgrc', 'w')
308 fp = repo.opener('hgrc', 'w')
312 fp.writelines(rcmaps)
309 fp.writelines(rcmaps)
313 fp.close()
310 fp.close()
314 ui.readconfig(repo.join('hgrc'))
311 ui.readconfig(repo.join('hgrc'))
315 kwmaps = dict(ui.configitems('keywordmaps'))
312 kwmaps = dict(ui.configitems('keywordmaps'))
316 elif opts.get('default'):
313 elif opts.get('default'):
317 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
314 ui.status(_('\n\tconfiguration using default keyword template maps\n'))
318 kwmaps = kwtemplater.templates
315 kwmaps = kwtemplater.templates
319 if uikwmaps:
316 if uikwmaps:
320 ui.status(_('\tdisabling current template maps\n'))
317 ui.status(_('\tdisabling current template maps\n'))
321 for k, v in kwmaps.iteritems():
318 for k, v in kwmaps.iteritems():
322 ui.setconfig('keywordmaps', k, v)
319 ui.setconfig('keywordmaps', k, v)
323 else:
320 else:
324 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
321 ui.status(_('\n\tconfiguration using current keyword template maps\n'))
325 kwmaps = dict(uikwmaps) or kwtemplater.templates
322 kwmaps = dict(uikwmaps) or kwtemplater.templates
326
323
327 uisetup(ui)
324 uisetup(ui)
328 reposetup(ui, repo)
325 reposetup(ui, repo)
329 for k, v in ui.configitems('extensions'):
326 for k, v in ui.configitems('extensions'):
330 if k.endswith('keyword'):
327 if k.endswith('keyword'):
331 extension = '%s = %s' % (k, v)
328 extension = '%s = %s' % (k, v)
332 break
329 break
333 ui.write('[extensions]\n%s\n' % extension)
330 ui.write('[extensions]\n%s\n' % extension)
334 demoitems('keyword', ui.configitems('keyword'))
331 demoitems('keyword', ui.configitems('keyword'))
335 demoitems('keywordmaps', kwmaps.iteritems())
332 demoitems('keywordmaps', kwmaps.iteritems())
336 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
333 keywords = '$' + '$\n$'.join(sorted(kwmaps.keys())) + '$\n'
337 repo.wopener(fn, 'w').write(keywords)
334 repo.wopener(fn, 'w').write(keywords)
338 repo.add([fn])
335 repo.add([fn])
339 path = repo.wjoin(fn)
336 path = repo.wjoin(fn)
340 ui.note(_('\nkeywords written to %s:\n') % path)
337 ui.note(_('\nkeywords written to %s:\n') % path)
341 ui.note(keywords)
338 ui.note(keywords)
342 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
339 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
343 # silence branch command if not verbose
340 # silence branch command if not verbose
344 quiet = ui.quiet
341 quiet = ui.quiet
345 ui.quiet = not ui.verbose
342 ui.quiet = not ui.verbose
346 commands.branch(ui, repo, branchname)
343 commands.branch(ui, repo, branchname)
347 ui.quiet = quiet
344 ui.quiet = quiet
348 for name, cmd in ui.configitems('hooks'):
345 for name, cmd in ui.configitems('hooks'):
349 if name.split('.', 1)[0].find('commit') > -1:
346 if name.split('.', 1)[0].find('commit') > -1:
350 repo.ui.setconfig('hooks', name, '')
347 repo.ui.setconfig('hooks', name, '')
351 ui.note(_('unhooked all commit hooks\n'))
348 ui.note(_('unhooked all commit hooks\n'))
352 msg = _('hg keyword configuration and expansion example')
349 msg = _('hg keyword configuration and expansion example')
353 ui.note("hg -R '%s' ci -m '%s'\n" % (tmpdir, msg))
350 ui.note("hg -R '%s' ci -m '%s'\n" % (tmpdir, msg))
354 repo.commit(text=msg)
351 repo.commit(text=msg)
355 ui.status(_('\n\tkeywords expanded\n'))
352 ui.status(_('\n\tkeywords expanded\n'))
356 ui.write(repo.wread(fn))
353 ui.write(repo.wread(fn))
357 ui.debug('\nremoving temporary repository %s\n' % tmpdir)
354 ui.debug('\nremoving temporary repository %s\n' % tmpdir)
358 shutil.rmtree(tmpdir, ignore_errors=True)
355 shutil.rmtree(tmpdir, ignore_errors=True)
359
356
360 def expand(ui, repo, *pats, **opts):
357 def expand(ui, repo, *pats, **opts):
361 '''expand keywords in the working directory
358 '''expand keywords in the working directory
362
359
363 Run after (re)enabling keyword expansion.
360 Run after (re)enabling keyword expansion.
364
361
365 kwexpand refuses to run if given files contain local changes.
362 kwexpand refuses to run if given files contain local changes.
366 '''
363 '''
367 # 3rd argument sets expansion to True
364 # 3rd argument sets expansion to True
368 _kwfwrite(ui, repo, True, *pats, **opts)
365 _kwfwrite(ui, repo, True, *pats, **opts)
369
366
370 def files(ui, repo, *pats, **opts):
367 def files(ui, repo, *pats, **opts):
371 '''show files configured for keyword expansion
368 '''show files configured for keyword expansion
372
369
373 List which files in the working directory are matched by the
370 List which files in the working directory are matched by the
374 [keyword] configuration patterns.
371 [keyword] configuration patterns.
375
372
376 Useful to prevent inadvertent keyword expansion and to speed up
373 Useful to prevent inadvertent keyword expansion and to speed up
377 execution by including only files that are actual candidates for
374 execution by including only files that are actual candidates for
378 expansion.
375 expansion.
379
376
380 See "hg help keyword" on how to construct patterns both for
377 See "hg help keyword" on how to construct patterns both for
381 inclusion and exclusion of files.
378 inclusion and exclusion of files.
382
379
383 With -A/--all and -v/--verbose the codes used to show the status
380 With -A/--all and -v/--verbose the codes used to show the status
384 of files are::
381 of files are::
385
382
386 K = keyword expansion candidate
383 K = keyword expansion candidate
387 k = keyword expansion candidate (not tracked)
384 k = keyword expansion candidate (not tracked)
388 I = ignored
385 I = ignored
389 i = ignored (not tracked)
386 i = ignored (not tracked)
390 '''
387 '''
391 kwt = kwtools['templater']
388 kwt = kwtools['templater']
392 status = _status(ui, repo, kwt, *pats, **opts)
389 status = _status(ui, repo, kwt, *pats, **opts)
393 cwd = pats and repo.getcwd() or ''
390 cwd = pats and repo.getcwd() or ''
394 modified, added, removed, deleted, unknown, ignored, clean = status
391 modified, added, removed, deleted, unknown, ignored, clean = status
395 files = []
392 files = []
396 if not opts.get('unknown') or opts.get('all'):
393 if not opts.get('unknown') or opts.get('all'):
397 files = sorted(modified + added + clean)
394 files = sorted(modified + added + clean)
398 wctx = repo[None]
395 wctx = repo[None]
399 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
396 kwfiles = [f for f in files if kwt.iskwfile(f, wctx.flags)]
400 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
397 kwunknown = [f for f in unknown if kwt.iskwfile(f, wctx.flags)]
401 if not opts.get('ignore') or opts.get('all'):
398 if not opts.get('ignore') or opts.get('all'):
402 showfiles = kwfiles, kwunknown
399 showfiles = kwfiles, kwunknown
403 else:
400 else:
404 showfiles = [], []
401 showfiles = [], []
405 if opts.get('all') or opts.get('ignore'):
402 if opts.get('all') or opts.get('ignore'):
406 showfiles += ([f for f in files if f not in kwfiles],
403 showfiles += ([f for f in files if f not in kwfiles],
407 [f for f in unknown if f not in kwunknown])
404 [f for f in unknown if f not in kwunknown])
408 for char, filenames in zip('KkIi', showfiles):
405 for char, filenames in zip('KkIi', showfiles):
409 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
406 fmt = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
410 for f in filenames:
407 for f in filenames:
411 ui.write(fmt % repo.pathto(f, cwd))
408 ui.write(fmt % repo.pathto(f, cwd))
412
409
413 def shrink(ui, repo, *pats, **opts):
410 def shrink(ui, repo, *pats, **opts):
414 '''revert expanded keywords in the working directory
411 '''revert expanded keywords in the working directory
415
412
416 Run before changing/disabling active keywords or if you experience
413 Run before changing/disabling active keywords or if you experience
417 problems with "hg import" or "hg merge".
414 problems with "hg import" or "hg merge".
418
415
419 kwshrink refuses to run if given files contain local changes.
416 kwshrink refuses to run if given files contain local changes.
420 '''
417 '''
421 # 3rd argument sets expansion to False
418 # 3rd argument sets expansion to False
422 _kwfwrite(ui, repo, False, *pats, **opts)
419 _kwfwrite(ui, repo, False, *pats, **opts)
423
420
424
421
425 def uisetup(ui):
422 def uisetup(ui):
426 '''Collects [keyword] config in kwtools.
423 '''Collects [keyword] config in kwtools.
427 Monkeypatches dispatch._parse if needed.'''
424 Monkeypatches dispatch._parse if needed.'''
428
425
429 for pat, opt in ui.configitems('keyword'):
426 for pat, opt in ui.configitems('keyword'):
430 if opt != 'ignore':
427 if opt != 'ignore':
431 kwtools['inc'].append(pat)
428 kwtools['inc'].append(pat)
432 else:
429 else:
433 kwtools['exc'].append(pat)
430 kwtools['exc'].append(pat)
434
431
435 if kwtools['inc']:
432 if kwtools['inc']:
436 def kwdispatch_parse(orig, ui, args):
433 def kwdispatch_parse(orig, ui, args):
437 '''Monkeypatch dispatch._parse to obtain running hg command.'''
434 '''Monkeypatch dispatch._parse to obtain running hg command.'''
438 cmd, func, args, options, cmdoptions = orig(ui, args)
435 cmd, func, args, options, cmdoptions = orig(ui, args)
439 kwtools['hgcmd'] = cmd
436 kwtools['hgcmd'] = cmd
440 return cmd, func, args, options, cmdoptions
437 return cmd, func, args, options, cmdoptions
441
438
442 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
439 extensions.wrapfunction(dispatch, '_parse', kwdispatch_parse)
443
440
444 def reposetup(ui, repo):
441 def reposetup(ui, repo):
445 '''Sets up repo as kwrepo for keyword substitution.
442 '''Sets up repo as kwrepo for keyword substitution.
446 Overrides file method to return kwfilelog instead of filelog
443 Overrides file method to return kwfilelog instead of filelog
447 if file matches user configuration.
444 if file matches user configuration.
448 Wraps commit to overwrite configured files with updated
445 Wraps commit to overwrite configured files with updated
449 keyword substitutions.
446 keyword substitutions.
450 Monkeypatches patch and webcommands.'''
447 Monkeypatches patch and webcommands.'''
451
448
452 try:
449 try:
453 if (not repo.local() or not kwtools['inc']
450 if (not repo.local() or not kwtools['inc']
454 or kwtools['hgcmd'] in nokwcommands.split()
451 or kwtools['hgcmd'] in nokwcommands.split()
455 or '.hg' in util.splitpath(repo.root)
452 or '.hg' in util.splitpath(repo.root)
456 or repo._url.startswith('bundle:')):
453 or repo._url.startswith('bundle:')):
457 return
454 return
458 except AttributeError:
455 except AttributeError:
459 pass
456 pass
460
457
461 kwtools['templater'] = kwt = kwtemplater(ui, repo)
458 kwtools['templater'] = kwt = kwtemplater(ui, repo)
462
459
463 class kwrepo(repo.__class__):
460 class kwrepo(repo.__class__):
464 def file(self, f):
461 def file(self, f):
465 if f[0] == '/':
462 if f[0] == '/':
466 f = f[1:]
463 f = f[1:]
467 return kwfilelog(self.sopener, kwt, f)
464 return kwfilelog(self.sopener, kwt, f)
468
465
469 def wread(self, filename):
466 def wread(self, filename):
470 data = super(kwrepo, self).wread(filename)
467 data = super(kwrepo, self).wread(filename)
471 return kwt.wread(filename, data)
468 return kwt.wread(filename, data)
472
469
473 def commit(self, *args, **opts):
470 def commit(self, *args, **opts):
474 # use custom commitctx for user commands
471 # use custom commitctx for user commands
475 # other extensions can still wrap repo.commitctx directly
472 # other extensions can still wrap repo.commitctx directly
476 self.commitctx = self.kwcommitctx
473 self.commitctx = self.kwcommitctx
477 try:
474 try:
478 return super(kwrepo, self).commit(*args, **opts)
475 return super(kwrepo, self).commit(*args, **opts)
479 finally:
476 finally:
480 del self.commitctx
477 del self.commitctx
481
478
482 def kwcommitctx(self, ctx, error=False):
479 def kwcommitctx(self, ctx, error=False):
483 n = super(kwrepo, self).commitctx(ctx, error)
480 n = super(kwrepo, self).commitctx(ctx, error)
484 # no lock needed, only called from repo.commit() which already locks
481 # no lock needed, only called from repo.commit() which already locks
485 kwt.overwrite(n, True, None)
482 kwt.overwrite(n, True, None)
486 return n
483 return n
487
484
488 # monkeypatches
485 # monkeypatches
489 def kwpatchfile_init(orig, self, ui, fname, opener,
486 def kwpatchfile_init(orig, self, ui, fname, opener,
490 missing=False, eol=None):
487 missing=False, eol=None):
491 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
488 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
492 rejects or conflicts due to expanded keywords in working dir.'''
489 rejects or conflicts due to expanded keywords in working dir.'''
493 orig(self, ui, fname, opener, missing, eol)
490 orig(self, ui, fname, opener, missing, eol)
494 # shrink keywords read from working dir
491 # shrink keywords read from working dir
495 self.lines = kwt.shrinklines(self.fname, self.lines)
492 self.lines = kwt.shrinklines(self.fname, self.lines)
496
493
497 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
494 def kw_diff(orig, repo, node1=None, node2=None, match=None, changes=None,
498 opts=None):
495 opts=None):
499 '''Monkeypatch patch.diff to avoid expansion except when
496 '''Monkeypatch patch.diff to avoid expansion except when
500 comparing against working dir.'''
497 comparing against working dir.'''
501 if node2 is not None:
498 if node2 is not None:
502 kwt.match = util.never
499 kwt.match = util.never
503 elif node1 is not None and node1 != repo['.'].node():
500 elif node1 is not None and node1 != repo['.'].node():
504 kwt.restrict = True
501 kwt.restrict = True
505 return orig(repo, node1, node2, match, changes, opts)
502 return orig(repo, node1, node2, match, changes, opts)
506
503
507 def kwweb_skip(orig, web, req, tmpl):
504 def kwweb_skip(orig, web, req, tmpl):
508 '''Wraps webcommands.x turning off keyword expansion.'''
505 '''Wraps webcommands.x turning off keyword expansion.'''
509 kwt.match = util.never
506 kwt.match = util.never
510 return orig(web, req, tmpl)
507 return orig(web, req, tmpl)
511
508
512 repo.__class__ = kwrepo
509 repo.__class__ = kwrepo
513
510
514 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
511 extensions.wrapfunction(patch.patchfile, '__init__', kwpatchfile_init)
515 if not kwt.restrict:
512 if not kwt.restrict:
516 extensions.wrapfunction(patch, 'diff', kw_diff)
513 extensions.wrapfunction(patch, 'diff', kw_diff)
517 for c in 'annotate changeset rev filediff diff'.split():
514 for c in 'annotate changeset rev filediff diff'.split():
518 extensions.wrapfunction(webcommands, c, kwweb_skip)
515 extensions.wrapfunction(webcommands, c, kwweb_skip)
519
516
520 cmdtable = {
517 cmdtable = {
521 'kwdemo':
518 'kwdemo':
522 (demo,
519 (demo,
523 [('d', 'default', None, _('show default keyword template maps')),
520 [('d', 'default', None, _('show default keyword template maps')),
524 ('f', 'rcfile', '', _('read maps from rcfile'))],
521 ('f', 'rcfile', '', _('read maps from rcfile'))],
525 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
522 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
526 'kwexpand': (expand, commands.walkopts,
523 'kwexpand': (expand, commands.walkopts,
527 _('hg kwexpand [OPTION]... [FILE]...')),
524 _('hg kwexpand [OPTION]... [FILE]...')),
528 'kwfiles':
525 'kwfiles':
529 (files,
526 (files,
530 [('A', 'all', None, _('show keyword status flags of all files')),
527 [('A', 'all', None, _('show keyword status flags of all files')),
531 ('i', 'ignore', None, _('show files excluded from expansion')),
528 ('i', 'ignore', None, _('show files excluded from expansion')),
532 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
529 ('u', 'unknown', None, _('only show unknown (not tracked) files')),
533 ] + commands.walkopts,
530 ] + commands.walkopts,
534 _('hg kwfiles [OPTION]... [FILE]...')),
531 _('hg kwfiles [OPTION]... [FILE]...')),
535 'kwshrink': (shrink, commands.walkopts,
532 'kwshrink': (shrink, commands.walkopts,
536 _('hg kwshrink [OPTION]... [FILE]...')),
533 _('hg kwshrink [OPTION]... [FILE]...')),
537 }
534 }
General Comments 0
You need to be logged in to leave comments. Login now