##// END OF EJS Templates
keyword: suppress keyword expansion for log commands...
Christian Ebert -
r5824:b8e8bd3c default
parent child Browse files
Show More
@@ -1,500 +1,501 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 verbose = ui.verbose
332 ui.quiet = not verbose
332 ui.quiet = not verbose
333 commands.branch(ui, repo, branchname)
333 commands.branch(ui, repo, branchname)
334 ui.quiet = quiet
334 ui.quiet = quiet
335 for name, cmd in ui.configitems('hooks'):
335 for name, cmd in ui.configitems('hooks'):
336 if name.split('.', 1)[0].find('commit') > -1:
336 if name.split('.', 1)[0].find('commit') > -1:
337 repo.ui.setconfig('hooks', name, '')
337 repo.ui.setconfig('hooks', name, '')
338 ui.note(_('unhooked all commit hooks\n'))
338 ui.note(_('unhooked all commit hooks\n'))
339 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
339 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
340 repo.commit(text=msg)
340 repo.commit(text=msg)
341 format = ui.verbose and ' in %s' % path or ''
341 format = ui.verbose and ' in %s' % path or ''
342 demostatus('%s keywords expanded%s' % (kwstatus, format))
342 demostatus('%s keywords expanded%s' % (kwstatus, format))
343 ui.write(repo.wread(fn))
343 ui.write(repo.wread(fn))
344 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
344 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
345 shutil.rmtree(tmpdir, ignore_errors=True)
345 shutil.rmtree(tmpdir, ignore_errors=True)
346
346
347 def expand(ui, repo, *pats, **opts):
347 def expand(ui, repo, *pats, **opts):
348 '''expand keywords in working directory
348 '''expand keywords in working directory
349
349
350 Run after (re)enabling keyword expansion.
350 Run after (re)enabling keyword expansion.
351
351
352 kwexpand refuses to run if given files contain local changes.
352 kwexpand refuses to run if given files contain local changes.
353 '''
353 '''
354 # 3rd argument sets expansion to True
354 # 3rd argument sets expansion to True
355 _kwfwrite(ui, repo, True, *pats, **opts)
355 _kwfwrite(ui, repo, True, *pats, **opts)
356
356
357 def files(ui, repo, *pats, **opts):
357 def files(ui, repo, *pats, **opts):
358 '''print files currently configured for keyword expansion
358 '''print files currently configured for keyword expansion
359
359
360 Crosscheck which files in working directory are potential targets for
360 Crosscheck which files in working directory are potential targets for
361 keyword expansion.
361 keyword expansion.
362 That is, files matched by [keyword] config patterns but not symlinks.
362 That is, files matched by [keyword] config patterns but not symlinks.
363 '''
363 '''
364 status = _status(ui, repo, *pats, **opts)
364 status = _status(ui, repo, *pats, **opts)
365 modified, added, removed, deleted, unknown, ignored, clean = status
365 modified, added, removed, deleted, unknown, ignored, clean = status
366 if opts.get('untracked'):
366 if opts.get('untracked'):
367 files = modified + added + unknown + clean
367 files = modified + added + unknown + clean
368 else:
368 else:
369 files = modified + added + clean
369 files = modified + added + clean
370 files.sort()
370 files.sort()
371 kwfiles = [f for f in files if _iskwfile(f, repo._link)]
371 kwfiles = [f for f in files if _iskwfile(f, repo._link)]
372 cwd = pats and repo.getcwd() or ''
372 cwd = pats and repo.getcwd() or ''
373 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
373 kwfstats = not opts.get('ignore') and (('K', kwfiles),) or ()
374 if opts.get('all') or opts.get('ignore'):
374 if opts.get('all') or opts.get('ignore'):
375 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
375 kwfstats += (('I', [f for f in files if f not in kwfiles]),)
376 for char, filenames in kwfstats:
376 for char, filenames in kwfstats:
377 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
377 format = (opts.get('all') or ui.verbose) and '%s %%s\n' % char or '%s\n'
378 for f in filenames:
378 for f in filenames:
379 ui.write(format % repo.pathto(f, cwd))
379 ui.write(format % repo.pathto(f, cwd))
380
380
381 def shrink(ui, repo, *pats, **opts):
381 def shrink(ui, repo, *pats, **opts):
382 '''revert expanded keywords in working directory
382 '''revert expanded keywords in working directory
383
383
384 Run before changing/disabling active keywords
384 Run before changing/disabling active keywords
385 or if you experience problems with "hg import" or "hg merge".
385 or if you experience problems with "hg import" or "hg merge".
386
386
387 kwshrink refuses to run if given files contain local changes.
387 kwshrink refuses to run if given files contain local changes.
388 '''
388 '''
389 # 3rd argument sets expansion to False
389 # 3rd argument sets expansion to False
390 _kwfwrite(ui, repo, False, *pats, **opts)
390 _kwfwrite(ui, repo, False, *pats, **opts)
391
391
392
392
393 def reposetup(ui, repo):
393 def reposetup(ui, repo):
394 '''Sets up repo as kwrepo for keyword substitution.
394 '''Sets up repo as kwrepo for keyword substitution.
395 Overrides file method to return kwfilelog instead of filelog
395 Overrides file method to return kwfilelog instead of filelog
396 if file matches user configuration.
396 if file matches user configuration.
397 Wraps commit to overwrite configured files with updated
397 Wraps commit to overwrite configured files with updated
398 keyword substitutions.
398 keyword substitutions.
399 This is done for local repos only, and only if there are
399 This is done for local repos only, and only if there are
400 files configured at all for keyword substitution.'''
400 files configured at all for keyword substitution.'''
401
401
402 def kwbailout():
402 def kwbailout():
403 '''Obtains command via simplified cmdline parsing,
403 '''Obtains command via simplified cmdline parsing,
404 returns True if keyword expansion not needed.'''
404 returns True if keyword expansion not needed.'''
405 nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
405 nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
406 'export', 'grep', 'identify', 'incoming', 'init',
406 'export', 'grep', 'identify', 'incoming', 'init',
407 'outgoing', 'push', 'remove', 'rename', 'rollback',
407 'log', 'outgoing', 'push', 'remove', 'rename',
408 'rollback', 'tip',
408 'convert')
409 'convert')
409 args = fancyopts.fancyopts(sys.argv[1:], commands.globalopts, {})
410 args = fancyopts.fancyopts(sys.argv[1:], commands.globalopts, {})
410 if args:
411 if args:
411 aliases, i = cmdutil.findcmd(ui, args[0], commands.table)
412 aliases, i = cmdutil.findcmd(ui, args[0], commands.table)
412 return aliases[0] in nokwcommands
413 return aliases[0] in nokwcommands
413
414
414 if not repo.local() or kwbailout():
415 if not repo.local() or kwbailout():
415 return
416 return
416
417
417 inc, exc = [], ['.hgtags']
418 inc, exc = [], ['.hgtags']
418 for pat, opt in ui.configitems('keyword'):
419 for pat, opt in ui.configitems('keyword'):
419 if opt != 'ignore':
420 if opt != 'ignore':
420 inc.append(pat)
421 inc.append(pat)
421 else:
422 else:
422 exc.append(pat)
423 exc.append(pat)
423 if not inc:
424 if not inc:
424 return
425 return
425
426
426 global _kwtemplater
427 global _kwtemplater
427 _kwtemplater = kwtemplater(ui, repo, inc, exc)
428 _kwtemplater = kwtemplater(ui, repo, inc, exc)
428
429
429 class kwrepo(repo.__class__):
430 class kwrepo(repo.__class__):
430 def file(self, f, kwmatch=False):
431 def file(self, f, kwmatch=False):
431 if f[0] == '/':
432 if f[0] == '/':
432 f = f[1:]
433 f = f[1:]
433 if kwmatch or _kwtemplater.matcher(f):
434 if kwmatch or _kwtemplater.matcher(f):
434 return kwfilelog(self.sopener, f)
435 return kwfilelog(self.sopener, f)
435 return filelog.filelog(self.sopener, f)
436 return filelog.filelog(self.sopener, f)
436
437
437 def commit(self, files=None, text='', user=None, date=None,
438 def commit(self, files=None, text='', user=None, date=None,
438 match=util.always, force=False, force_editor=False,
439 match=util.always, force=False, force_editor=False,
439 p1=None, p2=None, extra={}):
440 p1=None, p2=None, extra={}):
440 wlock = lock = None
441 wlock = lock = None
441 _p1 = _p2 = None
442 _p1 = _p2 = None
442 try:
443 try:
443 wlock = self.wlock()
444 wlock = self.wlock()
444 lock = self.lock()
445 lock = self.lock()
445 # store and postpone commit hooks
446 # store and postpone commit hooks
446 commithooks = []
447 commithooks = []
447 for name, cmd in ui.configitems('hooks'):
448 for name, cmd in ui.configitems('hooks'):
448 if name.split('.', 1)[0] == 'commit':
449 if name.split('.', 1)[0] == 'commit':
449 commithooks.append((name, cmd))
450 commithooks.append((name, cmd))
450 ui.setconfig('hooks', name, None)
451 ui.setconfig('hooks', name, None)
451 if commithooks:
452 if commithooks:
452 # store parents for commit hook environment
453 # store parents for commit hook environment
453 if p1 is None:
454 if p1 is None:
454 _p1, _p2 = repo.dirstate.parents()
455 _p1, _p2 = repo.dirstate.parents()
455 else:
456 else:
456 _p1, _p2 = p1, p2 or nullid
457 _p1, _p2 = p1, p2 or nullid
457 _p1 = hex(_p1)
458 _p1 = hex(_p1)
458 if _p2 == nullid:
459 if _p2 == nullid:
459 _p2 = ''
460 _p2 = ''
460 else:
461 else:
461 _p2 = hex(_p2)
462 _p2 = hex(_p2)
462
463
463 node = super(kwrepo,
464 node = super(kwrepo,
464 self).commit(files=files, text=text, user=user,
465 self).commit(files=files, text=text, user=user,
465 date=date, match=match, force=force,
466 date=date, match=match, force=force,
466 force_editor=force_editor,
467 force_editor=force_editor,
467 p1=p1, p2=p2, extra=extra)
468 p1=p1, p2=p2, extra=extra)
468
469
469 # restore commit hooks
470 # restore commit hooks
470 for name, cmd in commithooks:
471 for name, cmd in commithooks:
471 ui.setconfig('hooks', name, cmd)
472 ui.setconfig('hooks', name, cmd)
472 if node is not None:
473 if node is not None:
473 _overwrite(ui, self, node=node)
474 _overwrite(ui, self, node=node)
474 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
475 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
475 return node
476 return node
476 finally:
477 finally:
477 del wlock, lock
478 del wlock, lock
478
479
479 repo.__class__ = kwrepo
480 repo.__class__ = kwrepo
480 patch.patchfile.__init__ = _kwpatchfile_init
481 patch.patchfile.__init__ = _kwpatchfile_init
481
482
482
483
483 cmdtable = {
484 cmdtable = {
484 'kwdemo':
485 'kwdemo':
485 (demo,
486 (demo,
486 [('d', 'default', None, _('show default keyword template maps')),
487 [('d', 'default', None, _('show default keyword template maps')),
487 ('f', 'rcfile', [], _('read maps from rcfile'))],
488 ('f', 'rcfile', [], _('read maps from rcfile'))],
488 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
489 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
489 'kwexpand': (expand, commands.walkopts,
490 'kwexpand': (expand, commands.walkopts,
490 _('hg kwexpand [OPTION]... [FILE]...')),
491 _('hg kwexpand [OPTION]... [FILE]...')),
491 'kwfiles':
492 'kwfiles':
492 (files,
493 (files,
493 [('a', 'all', None, _('show keyword status flags of all files')),
494 [('a', 'all', None, _('show keyword status flags of all files')),
494 ('i', 'ignore', None, _('show files excluded from expansion')),
495 ('i', 'ignore', None, _('show files excluded from expansion')),
495 ('u', 'untracked', None, _('additionally show untracked files')),
496 ('u', 'untracked', None, _('additionally show untracked files')),
496 ] + commands.walkopts,
497 ] + commands.walkopts,
497 _('hg kwfiles [OPTION]... [FILE]...')),
498 _('hg kwfiles [OPTION]... [FILE]...')),
498 'kwshrink': (shrink, commands.walkopts,
499 'kwshrink': (shrink, commands.walkopts,
499 _('hg kwshrink [OPTION]... [FILE]...')),
500 _('hg kwshrink [OPTION]... [FILE]...')),
500 }
501 }
General Comments 0
You need to be logged in to leave comments. Login now