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