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