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