##// END OF EJS Templates
keyword: detect restricted commands thru variable
Christian Ebert -
r5961:ed4d55c2 default
parent child Browse files
Show More
@@ -1,521 +1,519 b''
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, 2008 Christian Ebert <blacktrash@gmx.net>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, 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 audience
14 # files (like LaTeX packages), that are mostly addressed to an audience
15 # not running a version control system.
15 # not running a version control system.
16 #
16 #
17 # For in-depth discussion refer to
17 # For in-depth discussion refer to
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/KeywordPlan>.
18 # <http://www.selenic.com/mercurial/wiki/index.cgi/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 # Setup in hgrc:
24 # Setup in hgrc:
25 #
25 #
26 # [extensions]
26 # [extensions]
27 # # enable extension
27 # # enable extension
28 # hgext.keyword =
28 # hgext.keyword =
29 #
29 #
30 # Files to act upon/ignore are specified in the [keyword] section.
30 # Files to act upon/ignore are specified in the [keyword] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
31 # Customized keyword template mappings in the [keywordmaps] section.
32 #
32 #
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
33 # Run "hg help keyword" and "hg kwdemo" to get info on configuration.
34
34
35 '''keyword expansion in local repositories
35 '''keyword expansion in local repositories
36
36
37 This extension expands RCS/CVS-like or self-customized $Keywords$
37 This extension expands RCS/CVS-like or self-customized $Keywords$
38 in tracked text files selected by your configuration.
38 in tracked text files selected by your configuration.
39
39
40 Keywords are only expanded in local repositories and not stored in
40 Keywords are only expanded in local repositories and not stored in
41 the change history. The mechanism can be regarded as a convenience
41 the change history. The mechanism can be regarded as a convenience
42 for the current user or for archive distribution.
42 for the current user or for archive distribution.
43
43
44 Configuration is done in the [keyword] and [keywordmaps] sections
44 Configuration is done in the [keyword] and [keywordmaps] sections
45 of hgrc files.
45 of hgrc files.
46
46
47 Example:
47 Example:
48
48
49 [keyword]
49 [keyword]
50 # expand keywords in every python file except those matching "x*"
50 # expand keywords in every python file except those matching "x*"
51 **.py =
51 **.py =
52 x* = ignore
52 x* = ignore
53
53
54 Note: the more specific you are in your filename patterns
54 Note: the more specific you are in your filename patterns
55 the less you lose speed in huge repos.
55 the less you lose speed in huge repos.
56
56
57 For [keywordmaps] template mapping and expansion demonstration and
57 For [keywordmaps] template mapping and expansion demonstration and
58 control run "hg kwdemo".
58 control run "hg kwdemo".
59
59
60 An additional date template filter {date|utcdate} is provided.
60 An additional date template filter {date|utcdate} is provided.
61
61
62 The default template mappings (view with "hg kwdemo -d") can be replaced
62 The default template mappings (view with "hg kwdemo -d") can be replaced
63 with customized keywords and templates.
63 with customized keywords and templates.
64 Again, run "hg kwdemo" to control the results of your config changes.
64 Again, run "hg kwdemo" to control the results of your config changes.
65
65
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
66 Before changing/disabling active keywords, run "hg kwshrink" to avoid
67 the risk of inadvertedly storing expanded keywords in the change history.
67 the risk of inadvertedly storing expanded keywords in the change history.
68
68
69 To force expansion after enabling it, or a configuration change, run
69 To force expansion after enabling it, or a configuration change, run
70 "hg kwexpand".
70 "hg kwexpand".
71
71
72 Also, when committing with the record extension or using mq's qrecord, be aware
72 Also, when committing with the record extension or using mq's qrecord, be aware
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
73 that keywords cannot be updated. Again, run "hg kwexpand" on the files in
74 question to update keyword expansions after all changes have been checked in.
74 question to update keyword expansions after all changes have been checked in.
75
75
76 Expansions spanning more than one line and incremental expansions,
76 Expansions spanning more than one line and incremental expansions,
77 like CVS' $Log$, are not supported. A keyword template map
77 like CVS' $Log$, are not supported. A keyword template map
78 "Log = {desc}" expands to the first line of the changeset description.
78 "Log = {desc}" expands to the first line of the changeset description.
79 '''
79 '''
80
80
81 from mercurial import commands, cmdutil, context, dispatch, filelog
81 from mercurial import commands, cmdutil, context, dispatch, filelog
82 from mercurial import patch, localrepo, revlog, templater, util
82 from mercurial import patch, localrepo, revlog, templater, util
83 from mercurial.node import *
83 from mercurial.node import *
84 from mercurial.i18n import _
84 from mercurial.i18n import _
85 import re, shutil, sys, tempfile, time
85 import re, shutil, sys, tempfile, time
86
86
87 commands.optionalrepo += ' kwdemo'
87 commands.optionalrepo += ' kwdemo'
88
88
89 # hg commands that trigger expansion only when writing to working dir,
90 # not when reading filelog, and unexpand when reading from working dir
91 restricted = ('diff1', 'record',
92 'qfold', 'qimport', 'qnew', 'qpush', 'qrefresh', 'qrecord')
93
89 def utcdate(date):
94 def utcdate(date):
90 '''Returns hgdate in cvs-like UTC format.'''
95 '''Returns hgdate in cvs-like UTC format.'''
91 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
96 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
92
97
93 def _kwrestrict(cmd):
94 '''Returns True if cmd should trigger restricted expansion.
95 Keywords will only expanded when writing to working dir.
96 Crucial for mq as expanded keywords should not make it into patches.'''
97 return cmd in ('diff1', 'record',
98 'qfold', 'qimport', 'qnew', 'qpush', 'qrefresh', 'qrecord')
99
100
98
101 _kwtemplater = None
99 _kwtemplater = None
102
100
103 class kwtemplater(object):
101 class kwtemplater(object):
104 '''
102 '''
105 Sets up keyword templates, corresponding keyword regex, and
103 Sets up keyword templates, corresponding keyword regex, and
106 provides keyword substitution functions.
104 provides keyword substitution functions.
107 '''
105 '''
108 templates = {
106 templates = {
109 'Revision': '{node|short}',
107 'Revision': '{node|short}',
110 'Author': '{author|user}',
108 'Author': '{author|user}',
111 'Date': '{date|utcdate}',
109 'Date': '{date|utcdate}',
112 'RCSFile': '{file|basename},v',
110 'RCSFile': '{file|basename},v',
113 'Source': '{root}/{file},v',
111 'Source': '{root}/{file},v',
114 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
112 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
115 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
113 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
116 }
114 }
117
115
118 def __init__(self, ui, repo, inc, exc, hgcmd):
116 def __init__(self, ui, repo, inc, exc, hgcmd):
119 self.ui = ui
117 self.ui = ui
120 self.repo = repo
118 self.repo = repo
121 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
119 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
122 self.hgcmd = hgcmd
120 self.hgcmd = hgcmd
123 self.commitnode = None
121 self.commitnode = None
124 self.path = ''
122 self.path = ''
125
123
126 kwmaps = self.ui.configitems('keywordmaps')
124 kwmaps = self.ui.configitems('keywordmaps')
127 if kwmaps: # override default templates
125 if kwmaps: # override default templates
128 kwmaps = [(k, templater.parsestring(v, quoted=False))
126 kwmaps = [(k, templater.parsestring(v, quoted=False))
129 for (k, v) in kwmaps]
127 for (k, v) in kwmaps]
130 self.templates = dict(kwmaps)
128 self.templates = dict(kwmaps)
131 escaped = map(re.escape, self.templates.keys())
129 escaped = map(re.escape, self.templates.keys())
132 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
130 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
133 self.re_kw = re.compile(kwpat)
131 self.re_kw = re.compile(kwpat)
134
132
135 templater.common_filters['utcdate'] = utcdate
133 templater.common_filters['utcdate'] = utcdate
136 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
134 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
137 False, '', False)
135 False, '', False)
138
136
139 def substitute(self, node, data, subfunc):
137 def substitute(self, node, data, subfunc):
140 '''Obtains file's changenode if commit node not given,
138 '''Obtains file's changenode if commit node not given,
141 and calls given substitution function.'''
139 and calls given substitution function.'''
142 if self.commitnode:
140 if self.commitnode:
143 fnode = self.commitnode
141 fnode = self.commitnode
144 else:
142 else:
145 c = context.filectx(self.repo, self.path, fileid=node)
143 c = context.filectx(self.repo, self.path, fileid=node)
146 fnode = c.node()
144 fnode = c.node()
147
145
148 def kwsub(mobj):
146 def kwsub(mobj):
149 '''Substitutes keyword using corresponding template.'''
147 '''Substitutes keyword using corresponding template.'''
150 kw = mobj.group(1)
148 kw = mobj.group(1)
151 self.ct.use_template(self.templates[kw])
149 self.ct.use_template(self.templates[kw])
152 self.ui.pushbuffer()
150 self.ui.pushbuffer()
153 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
151 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
154 return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer()))
152 return '$%s: %s $' % (kw, templater.firstline(self.ui.popbuffer()))
155
153
156 return subfunc(kwsub, data)
154 return subfunc(kwsub, data)
157
155
158 def expand(self, node, data):
156 def expand(self, node, data):
159 '''Returns data with keywords expanded.'''
157 '''Returns data with keywords expanded.'''
160 if util.binary(data) or _kwrestrict(self.hgcmd):
158 if util.binary(data) or self.hgcmd in restricted:
161 return data
159 return data
162 return self.substitute(node, data, self.re_kw.sub)
160 return self.substitute(node, data, self.re_kw.sub)
163
161
164 def process(self, node, data, expand):
162 def process(self, node, data, expand):
165 '''Returns a tuple: data, count.
163 '''Returns a tuple: data, count.
166 Count is number of keywords/keyword substitutions,
164 Count is number of keywords/keyword substitutions,
167 telling caller whether to act on file containing data.'''
165 telling caller whether to act on file containing data.'''
168 if util.binary(data):
166 if util.binary(data):
169 return data, None
167 return data, None
170 if expand:
168 if expand:
171 return self.substitute(node, data, self.re_kw.subn)
169 return self.substitute(node, data, self.re_kw.subn)
172 return data, self.re_kw.search(data)
170 return data, self.re_kw.search(data)
173
171
174 def shrink(self, text):
172 def shrink(self, text):
175 '''Returns text with all keyword substitutions removed.'''
173 '''Returns text with all keyword substitutions removed.'''
176 if util.binary(text):
174 if util.binary(text):
177 return text
175 return text
178 return self.re_kw.sub(r'$\1$', text)
176 return self.re_kw.sub(r'$\1$', text)
179
177
180 class kwfilelog(filelog.filelog):
178 class kwfilelog(filelog.filelog):
181 '''
179 '''
182 Subclass of filelog to hook into its read, add, cmp methods.
180 Subclass of filelog to hook into its read, add, cmp methods.
183 Keywords are "stored" unexpanded, and processed on reading.
181 Keywords are "stored" unexpanded, and processed on reading.
184 '''
182 '''
185 def __init__(self, opener, path):
183 def __init__(self, opener, path):
186 super(kwfilelog, self).__init__(opener, path)
184 super(kwfilelog, self).__init__(opener, path)
187 _kwtemplater.path = path
185 _kwtemplater.path = path
188
186
189 def kwctread(self, node, expand):
187 def kwctread(self, node, expand):
190 '''Reads expanding and counting keywords, called from _overwrite.'''
188 '''Reads expanding and counting keywords, called from _overwrite.'''
191 data = super(kwfilelog, self).read(node)
189 data = super(kwfilelog, self).read(node)
192 return _kwtemplater.process(node, data, expand)
190 return _kwtemplater.process(node, data, expand)
193
191
194 def read(self, node):
192 def read(self, node):
195 '''Expands keywords when reading filelog.'''
193 '''Expands keywords when reading filelog.'''
196 data = super(kwfilelog, self).read(node)
194 data = super(kwfilelog, self).read(node)
197 return _kwtemplater.expand(node, data)
195 return _kwtemplater.expand(node, data)
198
196
199 def add(self, text, meta, tr, link, p1=None, p2=None):
197 def add(self, text, meta, tr, link, p1=None, p2=None):
200 '''Removes keyword substitutions when adding to filelog.'''
198 '''Removes keyword substitutions when adding to filelog.'''
201 text = _kwtemplater.shrink(text)
199 text = _kwtemplater.shrink(text)
202 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
200 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
203
201
204 def cmp(self, node, text):
202 def cmp(self, node, text):
205 '''Removes keyword substitutions for comparison.'''
203 '''Removes keyword substitutions for comparison.'''
206 text = _kwtemplater.shrink(text)
204 text = _kwtemplater.shrink(text)
207 if self.renamed(node):
205 if self.renamed(node):
208 t2 = super(kwfilelog, self).read(node)
206 t2 = super(kwfilelog, self).read(node)
209 return t2 != text
207 return t2 != text
210 return revlog.revlog.cmp(self, node, text)
208 return revlog.revlog.cmp(self, node, text)
211
209
212
210
213 # store original patch.patchfile.__init__
211 # store original patch.patchfile.__init__
214 _patchfile_init = patch.patchfile.__init__
212 _patchfile_init = patch.patchfile.__init__
215
213
216 def _kwpatchfile_init(self, ui, fname, missing=False):
214 def _kwpatchfile_init(self, ui, fname, missing=False):
217 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
215 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
218 rejects or conflicts due to expanded keywords in working dir.'''
216 rejects or conflicts due to expanded keywords in working dir.'''
219 _patchfile_init(self, ui, fname, missing=missing)
217 _patchfile_init(self, ui, fname, missing=missing)
220
218
221 if _kwtemplater.matcher(self.fname):
219 if _kwtemplater.matcher(self.fname):
222 # shrink keywords read from working dir
220 # shrink keywords read from working dir
223 kwshrunk = _kwtemplater.shrink(''.join(self.lines))
221 kwshrunk = _kwtemplater.shrink(''.join(self.lines))
224 self.lines = kwshrunk.splitlines(True)
222 self.lines = kwshrunk.splitlines(True)
225
223
226
224
227 def _iskwfile(f, link):
225 def _iskwfile(f, link):
228 return not link(f) and _kwtemplater.matcher(f)
226 return not link(f) and _kwtemplater.matcher(f)
229
227
230 def _status(ui, repo, *pats, **opts):
228 def _status(ui, repo, *pats, **opts):
231 '''Bails out if [keyword] configuration is not active.
229 '''Bails out if [keyword] configuration is not active.
232 Returns status of working directory.'''
230 Returns status of working directory.'''
233 if _kwtemplater:
231 if _kwtemplater:
234 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
232 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
235 return repo.status(files=files, match=match, list_clean=True)
233 return repo.status(files=files, match=match, list_clean=True)
236 if ui.configitems('keyword'):
234 if ui.configitems('keyword'):
237 raise util.Abort(_('[keyword] patterns cannot match'))
235 raise util.Abort(_('[keyword] patterns cannot match'))
238 raise util.Abort(_('no [keyword] patterns configured'))
236 raise util.Abort(_('no [keyword] patterns configured'))
239
237
240 def _overwrite(ui, repo, node=None, expand=True, files=None):
238 def _overwrite(ui, repo, node=None, expand=True, files=None):
241 '''Overwrites selected files expanding/shrinking keywords.'''
239 '''Overwrites selected files expanding/shrinking keywords.'''
242 ctx = repo.changectx(node)
240 ctx = repo.changectx(node)
243 mf = ctx.manifest()
241 mf = ctx.manifest()
244 if node is not None: # commit
242 if node is not None: # commit
245 _kwtemplater.commitnode = node
243 _kwtemplater.commitnode = node
246 files = [f for f in ctx.files() if f in mf]
244 files = [f for f in ctx.files() if f in mf]
247 notify = ui.debug
245 notify = ui.debug
248 else: # kwexpand/kwshrink
246 else: # kwexpand/kwshrink
249 notify = ui.note
247 notify = ui.note
250 candidates = [f for f in files if _iskwfile(f, mf.linkf)]
248 candidates = [f for f in files if _iskwfile(f, mf.linkf)]
251 if candidates:
249 if candidates:
252 candidates.sort()
250 candidates.sort()
253 action = expand and 'expanding' or 'shrinking'
251 action = expand and 'expanding' or 'shrinking'
254 for f in candidates:
252 for f in candidates:
255 fp = repo.file(f, kwmatch=True)
253 fp = repo.file(f, kwmatch=True)
256 data, kwfound = fp.kwctread(mf[f], expand)
254 data, kwfound = fp.kwctread(mf[f], expand)
257 if kwfound:
255 if kwfound:
258 notify(_('overwriting %s %s keywords\n') % (f, action))
256 notify(_('overwriting %s %s keywords\n') % (f, action))
259 repo.wwrite(f, data, mf.flags(f))
257 repo.wwrite(f, data, mf.flags(f))
260 repo.dirstate.normal(f)
258 repo.dirstate.normal(f)
261
259
262 def _kwfwrite(ui, repo, expand, *pats, **opts):
260 def _kwfwrite(ui, repo, expand, *pats, **opts):
263 '''Selects files and passes them to _overwrite.'''
261 '''Selects files and passes them to _overwrite.'''
264 status = _status(ui, repo, *pats, **opts)
262 status = _status(ui, repo, *pats, **opts)
265 modified, added, removed, deleted, unknown, ignored, clean = status
263 modified, added, removed, deleted, unknown, ignored, clean = status
266 if modified or added or removed or deleted:
264 if modified or added or removed or deleted:
267 raise util.Abort(_('outstanding uncommitted changes in given files'))
265 raise util.Abort(_('outstanding uncommitted changes in given files'))
268 wlock = lock = None
266 wlock = lock = None
269 try:
267 try:
270 wlock = repo.wlock()
268 wlock = repo.wlock()
271 lock = repo.lock()
269 lock = repo.lock()
272 _overwrite(ui, repo, expand=expand, files=clean)
270 _overwrite(ui, repo, expand=expand, files=clean)
273 finally:
271 finally:
274 del wlock, lock
272 del wlock, lock
275
273
276
274
277 def demo(ui, repo, *args, **opts):
275 def demo(ui, repo, *args, **opts):
278 '''print [keywordmaps] configuration and an expansion example
276 '''print [keywordmaps] configuration and an expansion example
279
277
280 Show current, custom, or default keyword template maps
278 Show current, custom, or default keyword template maps
281 and their expansion.
279 and their expansion.
282
280
283 Extend current configuration by specifying maps as arguments
281 Extend current configuration by specifying maps as arguments
284 and optionally by reading from an additional hgrc file.
282 and optionally by reading from an additional hgrc file.
285
283
286 Override current keyword template maps with "default" option.
284 Override current keyword template maps with "default" option.
287 '''
285 '''
288 def demostatus(stat):
286 def demostatus(stat):
289 ui.status(_('\n\t%s\n') % stat)
287 ui.status(_('\n\t%s\n') % stat)
290
288
291 def demoitems(section, items):
289 def demoitems(section, items):
292 ui.write('[%s]\n' % section)
290 ui.write('[%s]\n' % section)
293 for k, v in items:
291 for k, v in items:
294 ui.write('%s = %s\n' % (k, v))
292 ui.write('%s = %s\n' % (k, v))
295
293
296 msg = 'hg keyword config and expansion example'
294 msg = 'hg keyword config and expansion example'
297 kwstatus = 'current'
295 kwstatus = 'current'
298 fn = 'demo.txt'
296 fn = 'demo.txt'
299 branchname = 'demobranch'
297 branchname = 'demobranch'
300 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
298 tmpdir = tempfile.mkdtemp('', 'kwdemo.')
301 ui.note(_('creating temporary repo at %s\n') % tmpdir)
299 ui.note(_('creating temporary repo at %s\n') % tmpdir)
302 repo = localrepo.localrepository(ui, path=tmpdir, create=True)
300 repo = localrepo.localrepository(ui, path=tmpdir, create=True)
303 ui.setconfig('keyword', fn, '')
301 ui.setconfig('keyword', fn, '')
304 if args or opts.get('rcfile'):
302 if args or opts.get('rcfile'):
305 kwstatus = 'custom'
303 kwstatus = 'custom'
306 if opts.get('rcfile'):
304 if opts.get('rcfile'):
307 ui.readconfig(opts.get('rcfile'))
305 ui.readconfig(opts.get('rcfile'))
308 if opts.get('default'):
306 if opts.get('default'):
309 kwstatus = 'default'
307 kwstatus = 'default'
310 kwmaps = kwtemplater.templates
308 kwmaps = kwtemplater.templates
311 if ui.configitems('keywordmaps'):
309 if ui.configitems('keywordmaps'):
312 # override maps from optional rcfile
310 # override maps from optional rcfile
313 for k, v in kwmaps.iteritems():
311 for k, v in kwmaps.iteritems():
314 ui.setconfig('keywordmaps', k, v)
312 ui.setconfig('keywordmaps', k, v)
315 elif args:
313 elif args:
316 # simulate hgrc parsing
314 # simulate hgrc parsing
317 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
315 rcmaps = ['[keywordmaps]\n'] + [a + '\n' for a in args]
318 fp = repo.opener('hgrc', 'w')
316 fp = repo.opener('hgrc', 'w')
319 fp.writelines(rcmaps)
317 fp.writelines(rcmaps)
320 fp.close()
318 fp.close()
321 ui.readconfig(repo.join('hgrc'))
319 ui.readconfig(repo.join('hgrc'))
322 if not opts.get('default'):
320 if not opts.get('default'):
323 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
321 kwmaps = dict(ui.configitems('keywordmaps')) or kwtemplater.templates
324 reposetup(ui, repo)
322 reposetup(ui, repo)
325 for k, v in ui.configitems('extensions'):
323 for k, v in ui.configitems('extensions'):
326 if k.endswith('keyword'):
324 if k.endswith('keyword'):
327 extension = '%s = %s' % (k, v)
325 extension = '%s = %s' % (k, v)
328 break
326 break
329 demostatus('config using %s keyword template maps' % kwstatus)
327 demostatus('config using %s keyword template maps' % kwstatus)
330 ui.write('[extensions]\n%s\n' % extension)
328 ui.write('[extensions]\n%s\n' % extension)
331 demoitems('keyword', ui.configitems('keyword'))
329 demoitems('keyword', ui.configitems('keyword'))
332 demoitems('keywordmaps', kwmaps.iteritems())
330 demoitems('keywordmaps', kwmaps.iteritems())
333 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
331 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
334 repo.wopener(fn, 'w').write(keywords)
332 repo.wopener(fn, 'w').write(keywords)
335 repo.add([fn])
333 repo.add([fn])
336 path = repo.wjoin(fn)
334 path = repo.wjoin(fn)
337 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
335 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
338 ui.note(keywords)
336 ui.note(keywords)
339 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
337 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
340 # silence branch command if not verbose
338 # silence branch command if not verbose
341 quiet = ui.quiet
339 quiet = ui.quiet
342 ui.quiet = not ui.verbose
340 ui.quiet = not ui.verbose
343 commands.branch(ui, repo, branchname)
341 commands.branch(ui, repo, branchname)
344 ui.quiet = quiet
342 ui.quiet = quiet
345 for name, cmd in ui.configitems('hooks'):
343 for name, cmd in ui.configitems('hooks'):
346 if name.split('.', 1)[0].find('commit') > -1:
344 if name.split('.', 1)[0].find('commit') > -1:
347 repo.ui.setconfig('hooks', name, '')
345 repo.ui.setconfig('hooks', name, '')
348 ui.note(_('unhooked all commit hooks\n'))
346 ui.note(_('unhooked all commit hooks\n'))
349 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
347 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
350 repo.commit(text=msg)
348 repo.commit(text=msg)
351 format = ui.verbose and ' in %s' % path or ''
349 format = ui.verbose and ' in %s' % path or ''
352 demostatus('%s keywords expanded%s' % (kwstatus, format))
350 demostatus('%s keywords expanded%s' % (kwstatus, format))
353 ui.write(repo.wread(fn))
351 ui.write(repo.wread(fn))
354 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
352 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
355 shutil.rmtree(tmpdir, ignore_errors=True)
353 shutil.rmtree(tmpdir, ignore_errors=True)
356
354
357 def expand(ui, repo, *pats, **opts):
355 def expand(ui, repo, *pats, **opts):
358 '''expand keywords in working directory
356 '''expand keywords in working directory
359
357
360 Run after (re)enabling keyword expansion.
358 Run after (re)enabling keyword expansion.
361
359
362 kwexpand refuses to run if given files contain local changes.
360 kwexpand refuses to run if given files contain local changes.
363 '''
361 '''
364 # 3rd argument sets expansion to True
362 # 3rd argument sets expansion to True
365 _kwfwrite(ui, repo, True, *pats, **opts)
363 _kwfwrite(ui, repo, True, *pats, **opts)
366
364
367 def files(ui, repo, *pats, **opts):
365 def files(ui, repo, *pats, **opts):
368 '''print files currently configured for keyword expansion
366 '''print files currently configured for keyword expansion
369
367
370 Crosscheck which files in working directory are potential targets for
368 Crosscheck which files in working directory are potential targets for
371 keyword expansion.
369 keyword expansion.
372 That is, files matched by [keyword] config patterns but not symlinks.
370 That is, files matched by [keyword] config patterns but not symlinks.
373 '''
371 '''
374 status = _status(ui, repo, *pats, **opts)
372 status = _status(ui, repo, *pats, **opts)
375 modified, added, removed, deleted, unknown, ignored, clean = status
373 modified, added, removed, deleted, unknown, ignored, clean = status
376 files = modified + added + clean
374 files = modified + added + clean
377 if opts.get('untracked'):
375 if opts.get('untracked'):
378 files += unknown
376 files += unknown
379 files.sort()
377 files.sort()
380 kwfiles = [f for f in files if _iskwfile(f, repo._link)]
378 kwfiles = [f for f in files if _iskwfile(f, repo._link)]
381 cwd = pats and repo.getcwd() or ''
379 cwd = pats and repo.getcwd() or ''
382 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
380 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
383 if opts.get('all') or opts.get('ignore'):
381 if opts.get('all') or opts.get('ignore'):
384 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
382 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
385 for char, filenames in kwfstats:
383 for char, filenames in kwfstats:
386 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
384 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
387 for f in filenames:
385 for f in filenames:
388 ui.write(format % repo.pathto(f, cwd))
386 ui.write(format % repo.pathto(f, cwd))
389
387
390 def shrink(ui, repo, *pats, **opts):
388 def shrink(ui, repo, *pats, **opts):
391 '''revert expanded keywords in working directory
389 '''revert expanded keywords in working directory
392
390
393 Run before changing/disabling active keywords
391 Run before changing/disabling active keywords
394 or if you experience problems with "hg import" or "hg merge".
392 or if you experience problems with "hg import" or "hg merge".
395
393
396 kwshrink refuses to run if given files contain local changes.
394 kwshrink refuses to run if given files contain local changes.
397 '''
395 '''
398 # 3rd argument sets expansion to False
396 # 3rd argument sets expansion to False
399 _kwfwrite(ui, repo, False, *pats, **opts)
397 _kwfwrite(ui, repo, False, *pats, **opts)
400
398
401
399
402 def reposetup(ui, repo):
400 def reposetup(ui, repo):
403 '''Sets up repo as kwrepo for keyword substitution.
401 '''Sets up repo as kwrepo for keyword substitution.
404 Overrides file method to return kwfilelog instead of filelog
402 Overrides file method to return kwfilelog instead of filelog
405 if file matches user configuration.
403 if file matches user configuration.
406 Wraps commit to overwrite configured files with updated
404 Wraps commit to overwrite configured files with updated
407 keyword substitutions.
405 keyword substitutions.
408 This is done for local repos only, and only if there are
406 This is done for local repos only, and only if there are
409 files configured at all for keyword substitution.'''
407 files configured at all for keyword substitution.'''
410
408
411 if not repo.local():
409 if not repo.local():
412 return
410 return
413
411
414 nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
412 nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
415 'export', 'grep', 'identify', 'incoming', 'init',
413 'export', 'grep', 'identify', 'incoming', 'init',
416 'log', 'outgoing', 'push', 'remove', 'rename',
414 'log', 'outgoing', 'push', 'remove', 'rename',
417 'rollback', 'tip',
415 'rollback', 'tip',
418 'convert')
416 'convert')
419 hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
417 hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
420 if hgcmd in nokwcommands:
418 if hgcmd in nokwcommands:
421 return
419 return
422
420
423 if hgcmd == 'diff':
421 if hgcmd == 'diff':
424 # only expand if comparing against working dir
422 # only expand if comparing against working dir
425 node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
423 node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
426 if node2 is not None:
424 if node2 is not None:
427 return
425 return
428 # shrink if rev is not current node
426 # shrink if rev is not current node
429 if node1 is not None and node1 != repo.changectx().node():
427 if node1 is not None and node1 != repo.changectx().node():
430 hgcmd = 'diff1'
428 hgcmd = 'diff1'
431
429
432 inc, exc = [], ['.hgtags']
430 inc, exc = [], ['.hgtags']
433 for pat, opt in ui.configitems('keyword'):
431 for pat, opt in ui.configitems('keyword'):
434 if opt != 'ignore':
432 if opt != 'ignore':
435 inc.append(pat)
433 inc.append(pat)
436 else:
434 else:
437 exc.append(pat)
435 exc.append(pat)
438 if not inc:
436 if not inc:
439 return
437 return
440
438
441 global _kwtemplater
439 global _kwtemplater
442 _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
440 _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
443
441
444 class kwrepo(repo.__class__):
442 class kwrepo(repo.__class__):
445 def file(self, f, kwmatch=False):
443 def file(self, f, kwmatch=False):
446 if f[0] == '/':
444 if f[0] == '/':
447 f = f[1:]
445 f = f[1:]
448 if kwmatch or _kwtemplater.matcher(f):
446 if kwmatch or _kwtemplater.matcher(f):
449 return kwfilelog(self.sopener, f)
447 return kwfilelog(self.sopener, f)
450 return filelog.filelog(self.sopener, f)
448 return filelog.filelog(self.sopener, f)
451
449
452 def wread(self, filename):
450 def wread(self, filename):
453 data = super(kwrepo, self).wread(filename)
451 data = super(kwrepo, self).wread(filename)
454 if _kwrestrict(hgcmd) and _kwtemplater.matcher(filename):
452 if hgcmd in restricted and _kwtemplater.matcher(filename):
455 return _kwtemplater.shrink(data)
453 return _kwtemplater.shrink(data)
456 return data
454 return data
457
455
458 def commit(self, files=None, text='', user=None, date=None,
456 def commit(self, files=None, text='', user=None, date=None,
459 match=util.always, force=False, force_editor=False,
457 match=util.always, force=False, force_editor=False,
460 p1=None, p2=None, extra={}):
458 p1=None, p2=None, extra={}):
461 wlock = lock = None
459 wlock = lock = None
462 _p1 = _p2 = None
460 _p1 = _p2 = None
463 try:
461 try:
464 wlock = self.wlock()
462 wlock = self.wlock()
465 lock = self.lock()
463 lock = self.lock()
466 # store and postpone commit hooks
464 # store and postpone commit hooks
467 commithooks = {}
465 commithooks = {}
468 for name, cmd in ui.configitems('hooks'):
466 for name, cmd in ui.configitems('hooks'):
469 if name.split('.', 1)[0] == 'commit':
467 if name.split('.', 1)[0] == 'commit':
470 commithooks[name] = cmd
468 commithooks[name] = cmd
471 ui.setconfig('hooks', name, None)
469 ui.setconfig('hooks', name, None)
472 if commithooks:
470 if commithooks:
473 # store parents for commit hook environment
471 # store parents for commit hook environment
474 if p1 is None:
472 if p1 is None:
475 _p1, _p2 = repo.dirstate.parents()
473 _p1, _p2 = repo.dirstate.parents()
476 else:
474 else:
477 _p1, _p2 = p1, p2 or nullid
475 _p1, _p2 = p1, p2 or nullid
478 _p1 = hex(_p1)
476 _p1 = hex(_p1)
479 if _p2 == nullid:
477 if _p2 == nullid:
480 _p2 = ''
478 _p2 = ''
481 else:
479 else:
482 _p2 = hex(_p2)
480 _p2 = hex(_p2)
483
481
484 node = super(kwrepo,
482 node = super(kwrepo,
485 self).commit(files=files, text=text, user=user,
483 self).commit(files=files, text=text, user=user,
486 date=date, match=match, force=force,
484 date=date, match=match, force=force,
487 force_editor=force_editor,
485 force_editor=force_editor,
488 p1=p1, p2=p2, extra=extra)
486 p1=p1, p2=p2, extra=extra)
489
487
490 # restore commit hooks
488 # restore commit hooks
491 for name, cmd in commithooks.iteritems():
489 for name, cmd in commithooks.iteritems():
492 ui.setconfig('hooks', name, cmd)
490 ui.setconfig('hooks', name, cmd)
493 if node is not None:
491 if node is not None:
494 _overwrite(ui, self, node=node)
492 _overwrite(ui, self, node=node)
495 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
493 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
496 return node
494 return node
497 finally:
495 finally:
498 del wlock, lock
496 del wlock, lock
499
497
500 repo.__class__ = kwrepo
498 repo.__class__ = kwrepo
501 patch.patchfile.__init__ = _kwpatchfile_init
499 patch.patchfile.__init__ = _kwpatchfile_init
502
500
503
501
504 cmdtable = {
502 cmdtable = {
505 'kwdemo':
503 'kwdemo':
506 (demo,
504 (demo,
507 [('d', 'default', None, _('show default keyword template maps')),
505 [('d', 'default', None, _('show default keyword template maps')),
508 ('f', 'rcfile', [], _('read maps from rcfile'))],
506 ('f', 'rcfile', [], _('read maps from rcfile'))],
509 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
507 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
510 'kwexpand': (expand, commands.walkopts,
508 'kwexpand': (expand, commands.walkopts,
511 _('hg kwexpand [OPTION]... [FILE]...')),
509 _('hg kwexpand [OPTION]... [FILE]...')),
512 'kwfiles':
510 'kwfiles':
513 (files,
511 (files,
514 [('a', 'all', None, _('show keyword status flags of all files')),
512 [('a', 'all', None, _('show keyword status flags of all files')),
515 ('i', 'ignore', None, _('show files excluded from expansion')),
513 ('i', 'ignore', None, _('show files excluded from expansion')),
516 ('u', 'untracked', None, _('additionally show untracked files')),
514 ('u', 'untracked', None, _('additionally show untracked files')),
517 ] + commands.walkopts,
515 ] + commands.walkopts,
518 _('hg kwfiles [OPTION]... [FILE]...')),
516 _('hg kwfiles [OPTION]... [FILE]...')),
519 'kwshrink': (shrink, commands.walkopts,
517 'kwshrink': (shrink, commands.walkopts,
520 _('hg kwshrink [OPTION]... [FILE]...')),
518 _('hg kwshrink [OPTION]... [FILE]...')),
521 }
519 }
General Comments 0
You need to be logged in to leave comments. Login now