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