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