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