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