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