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