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