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