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