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