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