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