##// END OF EJS Templates
keyword: allow keyword expansion on clone...
Christian Ebert -
r6021:717881b4 default
parent child Browse files
Show More
@@ -1,520 +1,519 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.node import *
83 from mercurial.node import *
84 from mercurial.i18n import _
84 from mercurial.i18n import _
85 import re, shutil, sys, tempfile, time
85 import re, shutil, sys, tempfile, time
86
86
87 commands.optionalrepo += ' kwdemo'
87 commands.optionalrepo += ' kwdemo'
88
88
89 # hg commands that trigger expansion only when writing to working dir,
89 # hg commands that trigger expansion only when writing to working dir,
90 # not when reading filelog, and unexpand when reading from working dir
90 # not when reading filelog, and unexpand when reading from working dir
91 restricted = ('diff1', 'record',
91 restricted = ('diff1', 'record',
92 'qfold', 'qimport', 'qnew', 'qpush', 'qrefresh', 'qrecord')
92 'qfold', 'qimport', 'qnew', 'qpush', 'qrefresh', 'qrecord')
93
93
94 def utcdate(date):
94 def utcdate(date):
95 '''Returns hgdate in cvs-like UTC format.'''
95 '''Returns hgdate in cvs-like UTC format.'''
96 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
96 return time.strftime('%Y/%m/%d %H:%M:%S', time.gmtime(date[0]))
97
97
98
98
99 _kwtemplater = None
99 _kwtemplater = None
100
100
101 class kwtemplater(object):
101 class kwtemplater(object):
102 '''
102 '''
103 Sets up keyword templates, corresponding keyword regex, and
103 Sets up keyword templates, corresponding keyword regex, and
104 provides keyword substitution functions.
104 provides keyword substitution functions.
105 '''
105 '''
106 templates = {
106 templates = {
107 'Revision': '{node|short}',
107 'Revision': '{node|short}',
108 'Author': '{author|user}',
108 'Author': '{author|user}',
109 'Date': '{date|utcdate}',
109 'Date': '{date|utcdate}',
110 'RCSFile': '{file|basename},v',
110 'RCSFile': '{file|basename},v',
111 'Source': '{root}/{file},v',
111 'Source': '{root}/{file},v',
112 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
112 'Id': '{file|basename},v {node|short} {date|utcdate} {author|user}',
113 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
113 'Header': '{root}/{file},v {node|short} {date|utcdate} {author|user}',
114 }
114 }
115
115
116 def __init__(self, ui, repo, inc, exc, hgcmd):
116 def __init__(self, ui, repo, inc, exc, hgcmd):
117 self.ui = ui
117 self.ui = ui
118 self.repo = repo
118 self.repo = repo
119 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
119 self.matcher = util.matcher(repo.root, inc=inc, exc=exc)[1]
120 self.hgcmd = hgcmd
120 self.hgcmd = hgcmd
121 self.commitnode = None
121 self.commitnode = None
122 self.path = ''
122 self.path = ''
123
123
124 kwmaps = self.ui.configitems('keywordmaps')
124 kwmaps = self.ui.configitems('keywordmaps')
125 if kwmaps: # override default templates
125 if kwmaps: # override default templates
126 kwmaps = [(k, templater.parsestring(v, quoted=False))
126 kwmaps = [(k, templater.parsestring(v, quoted=False))
127 for (k, v) in kwmaps]
127 for (k, v) in kwmaps]
128 self.templates = dict(kwmaps)
128 self.templates = dict(kwmaps)
129 escaped = map(re.escape, self.templates.keys())
129 escaped = map(re.escape, self.templates.keys())
130 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
130 kwpat = r'\$(%s)(: [^$\n\r]*? )??\$' % '|'.join(escaped)
131 self.re_kw = re.compile(kwpat)
131 self.re_kw = re.compile(kwpat)
132
132
133 templatefilters.filters['utcdate'] = utcdate
133 templatefilters.filters['utcdate'] = utcdate
134 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
134 self.ct = cmdutil.changeset_templater(self.ui, self.repo,
135 False, '', False)
135 False, '', False)
136
136
137 def substitute(self, node, data, subfunc):
137 def substitute(self, node, data, subfunc):
138 '''Obtains file's changenode if commit node not given,
138 '''Obtains file's changenode if commit node not given,
139 and calls given substitution function.'''
139 and calls given substitution function.'''
140 if self.commitnode:
140 if self.commitnode:
141 fnode = self.commitnode
141 fnode = self.commitnode
142 else:
142 else:
143 c = context.filectx(self.repo, self.path, fileid=node)
143 c = context.filectx(self.repo, self.path, fileid=node)
144 fnode = c.node()
144 fnode = c.node()
145
145
146 def kwsub(mobj):
146 def kwsub(mobj):
147 '''Substitutes keyword using corresponding template.'''
147 '''Substitutes keyword using corresponding template.'''
148 kw = mobj.group(1)
148 kw = mobj.group(1)
149 self.ct.use_template(self.templates[kw])
149 self.ct.use_template(self.templates[kw])
150 self.ui.pushbuffer()
150 self.ui.pushbuffer()
151 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
151 self.ct.show(changenode=fnode, root=self.repo.root, file=self.path)
152 return '$%s: %s $' % (kw, templatefilters.firstline(
152 return '$%s: %s $' % (kw, templatefilters.firstline(
153 self.ui.popbuffer()))
153 self.ui.popbuffer()))
154
154
155 return subfunc(kwsub, data)
155 return subfunc(kwsub, data)
156
156
157 def expand(self, node, data):
157 def expand(self, node, data):
158 '''Returns data with keywords expanded.'''
158 '''Returns data with keywords expanded.'''
159 if util.binary(data) or self.hgcmd in restricted:
159 if util.binary(data) or self.hgcmd in restricted:
160 return data
160 return data
161 return self.substitute(node, data, self.re_kw.sub)
161 return self.substitute(node, data, self.re_kw.sub)
162
162
163 def process(self, node, data, expand):
163 def process(self, node, data, expand):
164 '''Returns a tuple: data, count.
164 '''Returns a tuple: data, count.
165 Count is number of keywords/keyword substitutions,
165 Count is number of keywords/keyword substitutions,
166 telling caller whether to act on file containing data.'''
166 telling caller whether to act on file containing data.'''
167 if util.binary(data):
167 if util.binary(data):
168 return data, None
168 return data, None
169 if expand:
169 if expand:
170 return self.substitute(node, data, self.re_kw.subn)
170 return self.substitute(node, data, self.re_kw.subn)
171 return data, self.re_kw.search(data)
171 return data, self.re_kw.search(data)
172
172
173 def shrink(self, text):
173 def shrink(self, text):
174 '''Returns text with all keyword substitutions removed.'''
174 '''Returns text with all keyword substitutions removed.'''
175 if util.binary(text):
175 if util.binary(text):
176 return text
176 return text
177 return self.re_kw.sub(r'$\1$', text)
177 return self.re_kw.sub(r'$\1$', text)
178
178
179 class kwfilelog(filelog.filelog):
179 class kwfilelog(filelog.filelog):
180 '''
180 '''
181 Subclass of filelog to hook into its read, add, cmp methods.
181 Subclass of filelog to hook into its read, add, cmp methods.
182 Keywords are "stored" unexpanded, and processed on reading.
182 Keywords are "stored" unexpanded, and processed on reading.
183 '''
183 '''
184 def __init__(self, opener, path):
184 def __init__(self, opener, path):
185 super(kwfilelog, self).__init__(opener, path)
185 super(kwfilelog, self).__init__(opener, path)
186 _kwtemplater.path = path
186 _kwtemplater.path = path
187
187
188 def kwctread(self, node, expand):
188 def kwctread(self, node, expand):
189 '''Reads expanding and counting keywords, called from _overwrite.'''
189 '''Reads expanding and counting keywords, called from _overwrite.'''
190 data = super(kwfilelog, self).read(node)
190 data = super(kwfilelog, self).read(node)
191 return _kwtemplater.process(node, data, expand)
191 return _kwtemplater.process(node, data, expand)
192
192
193 def read(self, node):
193 def read(self, node):
194 '''Expands keywords when reading filelog.'''
194 '''Expands keywords when reading filelog.'''
195 data = super(kwfilelog, self).read(node)
195 data = super(kwfilelog, self).read(node)
196 return _kwtemplater.expand(node, data)
196 return _kwtemplater.expand(node, data)
197
197
198 def add(self, text, meta, tr, link, p1=None, p2=None):
198 def add(self, text, meta, tr, link, p1=None, p2=None):
199 '''Removes keyword substitutions when adding to filelog.'''
199 '''Removes keyword substitutions when adding to filelog.'''
200 text = _kwtemplater.shrink(text)
200 text = _kwtemplater.shrink(text)
201 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
201 return super(kwfilelog, self).add(text, meta, tr, link, p1=p1, p2=p2)
202
202
203 def cmp(self, node, text):
203 def cmp(self, node, text):
204 '''Removes keyword substitutions for comparison.'''
204 '''Removes keyword substitutions for comparison.'''
205 text = _kwtemplater.shrink(text)
205 text = _kwtemplater.shrink(text)
206 if self.renamed(node):
206 if self.renamed(node):
207 t2 = super(kwfilelog, self).read(node)
207 t2 = super(kwfilelog, self).read(node)
208 return t2 != text
208 return t2 != text
209 return revlog.revlog.cmp(self, node, text)
209 return revlog.revlog.cmp(self, node, text)
210
210
211
211
212 # store original patch.patchfile.__init__
212 # store original patch.patchfile.__init__
213 _patchfile_init = patch.patchfile.__init__
213 _patchfile_init = patch.patchfile.__init__
214
214
215 def _kwpatchfile_init(self, ui, fname, missing=False):
215 def _kwpatchfile_init(self, ui, fname, missing=False):
216 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
216 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
217 rejects or conflicts due to expanded keywords in working dir.'''
217 rejects or conflicts due to expanded keywords in working dir.'''
218 _patchfile_init(self, ui, fname, missing=missing)
218 _patchfile_init(self, ui, fname, missing=missing)
219
219
220 if _kwtemplater.matcher(self.fname):
220 if _kwtemplater.matcher(self.fname):
221 # shrink keywords read from working dir
221 # shrink keywords read from working dir
222 kwshrunk = _kwtemplater.shrink(''.join(self.lines))
222 kwshrunk = _kwtemplater.shrink(''.join(self.lines))
223 self.lines = kwshrunk.splitlines(True)
223 self.lines = kwshrunk.splitlines(True)
224
224
225
225
226 def _iskwfile(f, link):
226 def _iskwfile(f, link):
227 return not link(f) and _kwtemplater.matcher(f)
227 return not link(f) and _kwtemplater.matcher(f)
228
228
229 def _status(ui, repo, *pats, **opts):
229 def _status(ui, repo, *pats, **opts):
230 '''Bails out if [keyword] configuration is not active.
230 '''Bails out if [keyword] configuration is not active.
231 Returns status of working directory.'''
231 Returns status of working directory.'''
232 if _kwtemplater:
232 if _kwtemplater:
233 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
233 files, match, anypats = cmdutil.matchpats(repo, pats, opts)
234 return repo.status(files=files, match=match, list_clean=True)
234 return repo.status(files=files, match=match, list_clean=True)
235 if ui.configitems('keyword'):
235 if ui.configitems('keyword'):
236 raise util.Abort(_('[keyword] patterns cannot match'))
236 raise util.Abort(_('[keyword] patterns cannot match'))
237 raise util.Abort(_('no [keyword] patterns configured'))
237 raise util.Abort(_('no [keyword] patterns configured'))
238
238
239 def _overwrite(ui, repo, node=None, expand=True, files=None):
239 def _overwrite(ui, repo, node=None, expand=True, files=None):
240 '''Overwrites selected files expanding/shrinking keywords.'''
240 '''Overwrites selected files expanding/shrinking keywords.'''
241 ctx = repo.changectx(node)
241 ctx = repo.changectx(node)
242 mf = ctx.manifest()
242 mf = ctx.manifest()
243 if node is not None: # commit
243 if node is not None: # commit
244 _kwtemplater.commitnode = node
244 _kwtemplater.commitnode = node
245 files = [f for f in ctx.files() if f in mf]
245 files = [f for f in ctx.files() if f in mf]
246 notify = ui.debug
246 notify = ui.debug
247 else: # kwexpand/kwshrink
247 else: # kwexpand/kwshrink
248 notify = ui.note
248 notify = ui.note
249 candidates = [f for f in files if _iskwfile(f, mf.linkf)]
249 candidates = [f for f in files if _iskwfile(f, mf.linkf)]
250 if candidates:
250 if candidates:
251 candidates.sort()
251 candidates.sort()
252 action = expand and 'expanding' or 'shrinking'
252 action = expand and 'expanding' or 'shrinking'
253 for f in candidates:
253 for f in candidates:
254 fp = repo.file(f, kwmatch=True)
254 fp = repo.file(f, kwmatch=True)
255 data, kwfound = fp.kwctread(mf[f], expand)
255 data, kwfound = fp.kwctread(mf[f], expand)
256 if kwfound:
256 if kwfound:
257 notify(_('overwriting %s %s keywords\n') % (f, action))
257 notify(_('overwriting %s %s keywords\n') % (f, action))
258 repo.wwrite(f, data, mf.flags(f))
258 repo.wwrite(f, data, mf.flags(f))
259 repo.dirstate.normal(f)
259 repo.dirstate.normal(f)
260
260
261 def _kwfwrite(ui, repo, expand, *pats, **opts):
261 def _kwfwrite(ui, repo, expand, *pats, **opts):
262 '''Selects files and passes them to _overwrite.'''
262 '''Selects files and passes them to _overwrite.'''
263 status = _status(ui, repo, *pats, **opts)
263 status = _status(ui, repo, *pats, **opts)
264 modified, added, removed, deleted, unknown, ignored, clean = status
264 modified, added, removed, deleted, unknown, ignored, clean = status
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 in given files'))
266 raise util.Abort(_('outstanding uncommitted changes in given files'))
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 _overwrite(ui, repo, expand=expand, files=clean)
271 _overwrite(ui, repo, expand=expand, files=clean)
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, path=tmpdir, create=True)
301 repo = localrepo.localrepository(ui, path=tmpdir, create=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 reposetup(ui, repo)
323 reposetup(ui, repo)
324 for k, v in ui.configitems('extensions'):
324 for k, v in ui.configitems('extensions'):
325 if k.endswith('keyword'):
325 if k.endswith('keyword'):
326 extension = '%s = %s' % (k, v)
326 extension = '%s = %s' % (k, v)
327 break
327 break
328 demostatus('config using %s keyword template maps' % kwstatus)
328 demostatus('config using %s keyword template maps' % kwstatus)
329 ui.write('[extensions]\n%s\n' % extension)
329 ui.write('[extensions]\n%s\n' % extension)
330 demoitems('keyword', ui.configitems('keyword'))
330 demoitems('keyword', ui.configitems('keyword'))
331 demoitems('keywordmaps', kwmaps.iteritems())
331 demoitems('keywordmaps', kwmaps.iteritems())
332 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
332 keywords = '$' + '$\n$'.join(kwmaps.keys()) + '$\n'
333 repo.wopener(fn, 'w').write(keywords)
333 repo.wopener(fn, 'w').write(keywords)
334 repo.add([fn])
334 repo.add([fn])
335 path = repo.wjoin(fn)
335 path = repo.wjoin(fn)
336 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
336 ui.note(_('\n%s keywords written to %s:\n') % (kwstatus, path))
337 ui.note(keywords)
337 ui.note(keywords)
338 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
338 ui.note('\nhg -R "%s" branch "%s"\n' % (tmpdir, branchname))
339 # silence branch command if not verbose
339 # silence branch command if not verbose
340 quiet = ui.quiet
340 quiet = ui.quiet
341 ui.quiet = not ui.verbose
341 ui.quiet = not ui.verbose
342 commands.branch(ui, repo, branchname)
342 commands.branch(ui, repo, branchname)
343 ui.quiet = quiet
343 ui.quiet = quiet
344 for name, cmd in ui.configitems('hooks'):
344 for name, cmd in ui.configitems('hooks'):
345 if name.split('.', 1)[0].find('commit') > -1:
345 if name.split('.', 1)[0].find('commit') > -1:
346 repo.ui.setconfig('hooks', name, '')
346 repo.ui.setconfig('hooks', name, '')
347 ui.note(_('unhooked all commit hooks\n'))
347 ui.note(_('unhooked all commit hooks\n'))
348 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
348 ui.note('hg -R "%s" ci -m "%s"\n' % (tmpdir, msg))
349 repo.commit(text=msg)
349 repo.commit(text=msg)
350 format = ui.verbose and ' in %s' % path or ''
350 format = ui.verbose and ' in %s' % path or ''
351 demostatus('%s keywords expanded%s' % (kwstatus, format))
351 demostatus('%s keywords expanded%s' % (kwstatus, format))
352 ui.write(repo.wread(fn))
352 ui.write(repo.wread(fn))
353 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
353 ui.debug(_('\nremoving temporary repo %s\n') % tmpdir)
354 shutil.rmtree(tmpdir, ignore_errors=True)
354 shutil.rmtree(tmpdir, ignore_errors=True)
355
355
356 def expand(ui, repo, *pats, **opts):
356 def expand(ui, repo, *pats, **opts):
357 '''expand keywords in working directory
357 '''expand keywords in working directory
358
358
359 Run after (re)enabling keyword expansion.
359 Run after (re)enabling keyword expansion.
360
360
361 kwexpand refuses to run if given files contain local changes.
361 kwexpand refuses to run if given files contain local changes.
362 '''
362 '''
363 # 3rd argument sets expansion to True
363 # 3rd argument sets expansion to True
364 _kwfwrite(ui, repo, True, *pats, **opts)
364 _kwfwrite(ui, repo, True, *pats, **opts)
365
365
366 def files(ui, repo, *pats, **opts):
366 def files(ui, repo, *pats, **opts):
367 '''print files currently configured for keyword expansion
367 '''print files currently configured for keyword expansion
368
368
369 Crosscheck which files in working directory are potential targets for
369 Crosscheck which files in working directory are potential targets for
370 keyword expansion.
370 keyword expansion.
371 That is, files matched by [keyword] config patterns but not symlinks.
371 That is, files matched by [keyword] config patterns but not symlinks.
372 '''
372 '''
373 status = _status(ui, repo, *pats, **opts)
373 status = _status(ui, repo, *pats, **opts)
374 modified, added, removed, deleted, unknown, ignored, clean = status
374 modified, added, removed, deleted, unknown, ignored, clean = status
375 files = modified + added + clean
375 files = modified + added + clean
376 if opts.get('untracked'):
376 if opts.get('untracked'):
377 files += unknown
377 files += unknown
378 files.sort()
378 files.sort()
379 kwfiles = [f for f in files if _iskwfile(f, repo._link)]
379 kwfiles = [f for f in files if _iskwfile(f, repo._link)]
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 format = (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(format % 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 reposetup(ui, repo):
401 def reposetup(ui, repo):
402 '''Sets up repo as kwrepo for keyword substitution.
402 '''Sets up repo as kwrepo for keyword substitution.
403 Overrides file method to return kwfilelog instead of filelog
403 Overrides file method to return kwfilelog instead of filelog
404 if file matches user configuration.
404 if file matches user configuration.
405 Wraps commit to overwrite configured files with updated
405 Wraps commit to overwrite configured files with updated
406 keyword substitutions.
406 keyword substitutions.
407 This is done for local repos only, and only if there are
407 This is done for local repos only, and only if there are
408 files configured at all for keyword substitution.'''
408 files configured at all for keyword substitution.'''
409
409
410 if not repo.local():
410 if not repo.local():
411 return
411 return
412
412
413 nokwcommands = ('add', 'addremove', 'bundle', 'clone', 'copy',
413 nokwcommands = ('add', 'addremove', 'bundle', 'copy', 'export', 'grep',
414 'export', 'grep', 'identify', 'incoming', 'init',
414 'identify', 'incoming', 'init', 'log', 'outgoing', 'push',
415 'log', 'outgoing', 'push', 'remove', 'rename',
415 'remove', 'rename', 'rollback', 'tip',
416 'rollback', 'tip',
417 'convert')
416 'convert')
418 hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
417 hgcmd, func, args, opts, cmdopts = dispatch._parse(ui, sys.argv[1:])
419 if hgcmd in nokwcommands:
418 if hgcmd in nokwcommands:
420 return
419 return
421
420
422 if hgcmd == 'diff':
421 if hgcmd == 'diff':
423 # only expand if comparing against working dir
422 # only expand if comparing against working dir
424 node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
423 node1, node2 = cmdutil.revpair(repo, cmdopts.get('rev'))
425 if node2 is not None:
424 if node2 is not None:
426 return
425 return
427 # shrink if rev is not current node
426 # shrink if rev is not current node
428 if node1 is not None and node1 != repo.changectx().node():
427 if node1 is not None and node1 != repo.changectx().node():
429 hgcmd = 'diff1'
428 hgcmd = 'diff1'
430
429
431 inc, exc = [], ['.hgtags']
430 inc, exc = [], ['.hgtags']
432 for pat, opt in ui.configitems('keyword'):
431 for pat, opt in ui.configitems('keyword'):
433 if opt != 'ignore':
432 if opt != 'ignore':
434 inc.append(pat)
433 inc.append(pat)
435 else:
434 else:
436 exc.append(pat)
435 exc.append(pat)
437 if not inc:
436 if not inc:
438 return
437 return
439
438
440 global _kwtemplater
439 global _kwtemplater
441 _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
440 _kwtemplater = kwtemplater(ui, repo, inc, exc, hgcmd)
442
441
443 class kwrepo(repo.__class__):
442 class kwrepo(repo.__class__):
444 def file(self, f, kwmatch=False):
443 def file(self, f, kwmatch=False):
445 if f[0] == '/':
444 if f[0] == '/':
446 f = f[1:]
445 f = f[1:]
447 if kwmatch or _kwtemplater.matcher(f):
446 if kwmatch or _kwtemplater.matcher(f):
448 return kwfilelog(self.sopener, f)
447 return kwfilelog(self.sopener, f)
449 return filelog.filelog(self.sopener, f)
448 return filelog.filelog(self.sopener, f)
450
449
451 def wread(self, filename):
450 def wread(self, filename):
452 data = super(kwrepo, self).wread(filename)
451 data = super(kwrepo, self).wread(filename)
453 if hgcmd in restricted and _kwtemplater.matcher(filename):
452 if hgcmd in restricted and _kwtemplater.matcher(filename):
454 return _kwtemplater.shrink(data)
453 return _kwtemplater.shrink(data)
455 return data
454 return data
456
455
457 def commit(self, files=None, text='', user=None, date=None,
456 def commit(self, files=None, text='', user=None, date=None,
458 match=util.always, force=False, force_editor=False,
457 match=util.always, force=False, force_editor=False,
459 p1=None, p2=None, extra={}):
458 p1=None, p2=None, extra={}):
460 wlock = lock = None
459 wlock = lock = None
461 _p1 = _p2 = None
460 _p1 = _p2 = None
462 try:
461 try:
463 wlock = self.wlock()
462 wlock = self.wlock()
464 lock = self.lock()
463 lock = self.lock()
465 # store and postpone commit hooks
464 # store and postpone commit hooks
466 commithooks = {}
465 commithooks = {}
467 for name, cmd in ui.configitems('hooks'):
466 for name, cmd in ui.configitems('hooks'):
468 if name.split('.', 1)[0] == 'commit':
467 if name.split('.', 1)[0] == 'commit':
469 commithooks[name] = cmd
468 commithooks[name] = cmd
470 ui.setconfig('hooks', name, None)
469 ui.setconfig('hooks', name, None)
471 if commithooks:
470 if commithooks:
472 # store parents for commit hook environment
471 # store parents for commit hook environment
473 if p1 is None:
472 if p1 is None:
474 _p1, _p2 = repo.dirstate.parents()
473 _p1, _p2 = repo.dirstate.parents()
475 else:
474 else:
476 _p1, _p2 = p1, p2 or nullid
475 _p1, _p2 = p1, p2 or nullid
477 _p1 = hex(_p1)
476 _p1 = hex(_p1)
478 if _p2 == nullid:
477 if _p2 == nullid:
479 _p2 = ''
478 _p2 = ''
480 else:
479 else:
481 _p2 = hex(_p2)
480 _p2 = hex(_p2)
482
481
483 node = super(kwrepo,
482 node = super(kwrepo,
484 self).commit(files=files, text=text, user=user,
483 self).commit(files=files, text=text, user=user,
485 date=date, match=match, force=force,
484 date=date, match=match, force=force,
486 force_editor=force_editor,
485 force_editor=force_editor,
487 p1=p1, p2=p2, extra=extra)
486 p1=p1, p2=p2, extra=extra)
488
487
489 # restore commit hooks
488 # restore commit hooks
490 for name, cmd in commithooks.iteritems():
489 for name, cmd in commithooks.iteritems():
491 ui.setconfig('hooks', name, cmd)
490 ui.setconfig('hooks', name, cmd)
492 if node is not None:
491 if node is not None:
493 _overwrite(ui, self, node=node)
492 _overwrite(ui, self, node=node)
494 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
493 repo.hook('commit', node=node, parent1=_p1, parent2=_p2)
495 return node
494 return node
496 finally:
495 finally:
497 del wlock, lock
496 del wlock, lock
498
497
499 repo.__class__ = kwrepo
498 repo.__class__ = kwrepo
500 patch.patchfile.__init__ = _kwpatchfile_init
499 patch.patchfile.__init__ = _kwpatchfile_init
501
500
502
501
503 cmdtable = {
502 cmdtable = {
504 'kwdemo':
503 'kwdemo':
505 (demo,
504 (demo,
506 [('d', 'default', None, _('show default keyword template maps')),
505 [('d', 'default', None, _('show default keyword template maps')),
507 ('f', 'rcfile', [], _('read maps from rcfile'))],
506 ('f', 'rcfile', [], _('read maps from rcfile'))],
508 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
507 _('hg kwdemo [-d] [-f RCFILE] [TEMPLATEMAP]...')),
509 'kwexpand': (expand, commands.walkopts,
508 'kwexpand': (expand, commands.walkopts,
510 _('hg kwexpand [OPTION]... [FILE]...')),
509 _('hg kwexpand [OPTION]... [FILE]...')),
511 'kwfiles':
510 'kwfiles':
512 (files,
511 (files,
513 [('a', 'all', None, _('show keyword status flags of all files')),
512 [('a', 'all', None, _('show keyword status flags of all files')),
514 ('i', 'ignore', None, _('show files excluded from expansion')),
513 ('i', 'ignore', None, _('show files excluded from expansion')),
515 ('u', 'untracked', None, _('additionally show untracked files')),
514 ('u', 'untracked', None, _('additionally show untracked files')),
516 ] + commands.walkopts,
515 ] + commands.walkopts,
517 _('hg kwfiles [OPTION]... [FILE]...')),
516 _('hg kwfiles [OPTION]... [FILE]...')),
518 'kwshrink': (shrink, commands.walkopts,
517 'kwshrink': (shrink, commands.walkopts,
519 _('hg kwshrink [OPTION]... [FILE]...')),
518 _('hg kwshrink [OPTION]... [FILE]...')),
520 }
519 }
General Comments 0
You need to be logged in to leave comments. Login now